From 51515026650d7d255437bc440906c7f9a4e9a3c6 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:05:16 +1000 Subject: [PATCH] doc comments --- .../IOutputBuilder.VisitComment.cs | 10 ++ .../CSharpOutputBuilder.VisitComment.cs | 140 ++++++++++++++++++ .../PInvokeGenerator.VisitComment.cs | 17 +++ .../PInvokeGenerator.VisitDecl.cs | 6 + .../PInvokeGenerator.VisitRecordDecl.cs | 4 + .../PInvokeGenerator.VisitVarDecl.cs | 1 + .../PInvokeGeneratorConfiguration.cs | 2 + .../PInvokeGeneratorConfigurationOptions.cs | 2 + .../XML/XmlOutputBuilder.VisitComment.cs | 13 ++ .../Program.Options.cs | 1 + sources/ClangSharpPInvokeGenerator/Program.cs | 1 + 11 files changed, 197 insertions(+) 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/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 5b3af0ed..56006a4f 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -367,6 +367,7 @@ private void VisitEnumConstantDecl(EnumConstantDecl enumConstantDecl) CustomAttrGeneratorData = (enumConstantDecl, this), }; + WriteDocCommentXml(enumConstantDecl.Handle.ParsedComment); _outputBuilder.BeginValue(in desc); if ((enumConstantDecl.InitExpr != null) && (!ShouldConstantFoldValue(enumConstantDecl) || IsEnumeratorAliasInitializer(enumConstantDecl))) @@ -512,6 +513,7 @@ private void VisitEnumDecl(EnumDecl enumDecl) CustomAttrGeneratorData = (enumDecl, this), }; + WriteDocCommentXml(enumDecl.Handle.ParsedComment); _outputBuilder.BeginEnum(in desc); } @@ -573,6 +575,7 @@ private void VisitFieldDecl(FieldDecl fieldDecl) CustomAttrGeneratorData = (fieldDecl, this), }; + WriteDocCommentXml(fieldDecl.Handle.ParsedComment); _outputBuilder.BeginField(in desc); if (IsTypeConstantOrIncompleteArray(fieldDecl, type, out var arrayType)) @@ -679,6 +682,8 @@ private void VisitFunctionDecl(FunctionDecl functionDecl) TransformBoolType(ref returnTypeName, ref nativeTypeName); } + WriteDocCommentXml(functionDecl.Handle.ParsedComment); + var type = functionDecl.Type; var callingConventionName = GetCallingConvention(functionDecl, cxxRecordDecl, type); @@ -1576,6 +1581,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); diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs index 0cd2e447..bc62c464 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs @@ -245,6 +245,8 @@ private void VisitRecordDecl(RecordDecl recordDecl) }; Debug.Assert(_outputBuilder is not null); + WriteDocCommentXml(recordDecl.Handle.ParsedComment); + if (!isTopLevelStruct) { _outputBuilder.BeginStruct(in desc); @@ -829,6 +831,7 @@ void OutputMarkerInterface(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethodD }; var isUnsafe = true; + WriteDocCommentXml(cxxMethodDecl.Handle.ParsedComment); _outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe); _outputBuilder.BeginFunctionInnerPrototype(in desc); @@ -1000,6 +1003,7 @@ void OutputVtblHelperMethod(CXXRecordDecl cxxRecordDecl, CXXMethodDecl cxxMethod }; var isUnsafe = true; + WriteDocCommentXml(cxxMethodDecl.Handle.ParsedComment); _outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe); _outputBuilder.BeginFunctionInnerPrototype(in desc); diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs index bd966fa4..06ef929a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs @@ -256,6 +256,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 d66341a9..4e98a977 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs @@ -400,6 +400,8 @@ public GeneratedCodeAttributeMode GeneratedCodeAttributeMode public bool DontUseUsingStaticsForGuidMember => (_options & PInvokeGeneratorConfigurationOptions.DontUseUsingStaticsForGuidMember) != 0; + 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 9082c856..82ebce2a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs @@ -102,4 +102,6 @@ public enum PInvokeGeneratorConfigurationOptions : long GenerateObjectiveCBindings = 1L << 45, GenerateNativeAlignmentAttribute = 1L << 46, + + GenerateDocComments = 1L << 47, } 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/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs index 404b87d1..e862ac69 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.Options.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs @@ -237,6 +237,7 @@ internal static partial class Program new HelpRow("default-remappings", "Default remappings for well known types should be added. This currently includes intptr_t, ptrdiff_t, size_t, ssize_t, uintptr_t, and the exact-width stdint types (int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, and uint64_t). When targeting Windows, the pointer-width Windows types (INT_PTR, LONG_PTR, SSIZE_T, DWORD_PTR, SIZE_T, UINT_PTR, and ULONG_PTR) and _GUID are also included. On by default; use =false to opt out."), new HelpRow("disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."), new HelpRow("doc-includes", " xml documentation tags should be generated for declarations."), + new HelpRow("doc-comments", "Xml doc comments should be generated from encountered doxygen comments."), new HelpRow("empty-records", "Bindings for records that contain no members should be generated. These are commonly encountered for opaque handle like types such as HWND. On by default; use =false to opt out."), new HelpRow("enum-member-type-name", "The enum type name should be kept at the beginning of its member names. On by default; use =false to strip it."), new HelpRow("enum-operators", "Bindings for operators over enum types should be generated. These are largely unnecessary in C# as the operators are available by default. On by default; use =false to opt out."), diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index e68d7270..d6fea640 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -40,6 +40,7 @@ internal static partial class Program ["generate-cpp-attributes"] = (GenerateCppAttributes, false), ["generate-disable-runtime-marshalling"] = (GenerateDisableRuntimeMarshalling, false), ["generate-doc-includes"] = (GenerateDocIncludes, false), + ["generate-doc-comments"] = (GenerateDocComments, false), ["generate-extern-variables"] = (GenerateExternVariables, false), ["generate-file-scoped-namespaces"] = (GenerateFileScopedNamespaces, false), ["generate-fixed-buffer-indexer-overloads"] = (GenerateFixedBufferIndexerOverloads, false),