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/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;