From 2957b198893c9a5c3b1388cdbcc782c5621f5900 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Feb 2026 23:36:40 +0000
Subject: [PATCH] WIP: Auto-generate triple-slash comments without additional
packages
Co-authored-by: omegaleo <5655201+omegaleo@users.noreply.github.com>
---
...OmegaLeo.HelperLib.CommentGenerator.csproj | 20 ++
.../Program.cs | 275 ++++++++++++++++++
.../Helpers/BenchmarkUtility.cs | 57 +++-
3 files changed, 343 insertions(+), 9 deletions(-)
create mode 100644 OmegaLeo.HelperLib.CommentGenerator/OmegaLeo.HelperLib.CommentGenerator.csproj
create mode 100644 OmegaLeo.HelperLib.CommentGenerator/Program.cs
diff --git a/OmegaLeo.HelperLib.CommentGenerator/OmegaLeo.HelperLib.CommentGenerator.csproj b/OmegaLeo.HelperLib.CommentGenerator/OmegaLeo.HelperLib.CommentGenerator.csproj
new file mode 100644
index 0000000..f1f76fe
--- /dev/null
+++ b/OmegaLeo.HelperLib.CommentGenerator/OmegaLeo.HelperLib.CommentGenerator.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ latest
+
+
+
diff --git a/OmegaLeo.HelperLib.CommentGenerator/Program.cs b/OmegaLeo.HelperLib.CommentGenerator/Program.cs
new file mode 100644
index 0000000..a1fbca9
--- /dev/null
+++ b/OmegaLeo.HelperLib.CommentGenerator/Program.cs
@@ -0,0 +1,275 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace OmegaLeo.HelperLib.CommentGenerator;
+
+class Program
+{
+ static int Main(string[] args)
+ {
+ if (args.Length == 0)
+ {
+ Console.WriteLine("Usage: dotnet run ");
+ Console.WriteLine("Generates triple-slash comments from DocumentationAttribute in C# files.");
+ return 1;
+ }
+
+ var directory = args[0];
+ if (!Directory.Exists(directory))
+ {
+ Console.Error.WriteLine($"Error: Directory '{directory}' does not exist.");
+ return 1;
+ }
+
+ var csFiles = Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories)
+ .Where(f => !f.Contains("/obj/") && !f.Contains("\\obj\\") &&
+ !f.Contains("/bin/") && !f.Contains("\\bin\\"))
+ .ToList();
+
+ Console.WriteLine($"[CommentGenerator] Processing {csFiles.Count} C# files in {directory}...");
+
+ int modifiedCount = 0;
+ foreach (var filePath in csFiles)
+ {
+ if (ProcessFile(filePath))
+ {
+ modifiedCount++;
+ }
+ }
+
+ Console.WriteLine($"[CommentGenerator] ✅ Complete! Modified {modifiedCount} files.");
+ return 0;
+ }
+
+ static bool ProcessFile(string filePath)
+ {
+ try
+ {
+ var code = File.ReadAllText(filePath);
+ var tree = CSharpSyntaxTree.ParseText(code);
+ var root = tree.GetRoot();
+
+ var rewriter = new DocumentationCommentRewriter();
+ var newRoot = rewriter.Visit(root);
+
+ if (rewriter.Modified)
+ {
+ File.WriteAllText(filePath, newRoot.ToFullString());
+ Console.WriteLine($" ✓ {Path.GetFileName(filePath)}");
+ return true;
+ }
+
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($" ✗ Error processing {Path.GetFileName(filePath)}: {ex.Message}");
+ return false;
+ }
+ }
+}
+
+class DocumentationCommentRewriter : CSharpSyntaxRewriter
+{
+ public bool Modified { get; private set; }
+
+ public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
+ {
+ node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node)!;
+ return AddDocumentationComment(node);
+ }
+
+ public override SyntaxNode? VisitStructDeclaration(StructDeclarationSyntax node)
+ {
+ node = (StructDeclarationSyntax)base.VisitStructDeclaration(node)!;
+ return AddDocumentationComment(node);
+ }
+
+ public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
+ {
+ node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!;
+ return AddDocumentationComment(node);
+ }
+
+ public override SyntaxNode? VisitPropertyDeclaration(PropertyDeclarationSyntax node)
+ {
+ node = (PropertyDeclarationSyntax)base.VisitPropertyDeclaration(node)!;
+ return AddDocumentationComment(node);
+ }
+
+ private T AddDocumentationComment(T node) where T : MemberDeclarationSyntax
+ {
+ // Check if already has /// comments
+ if (HasXmlDocumentation(node))
+ {
+ return node;
+ }
+
+ // Find DocumentationAttribute
+ var docAttr = node.AttributeLists
+ .SelectMany(al => al.Attributes)
+ .FirstOrDefault(a => a.Name.ToString().Contains("Documentation"));
+
+ if (docAttr == null)
+ {
+ return node;
+ }
+
+ // Extract attribute arguments
+ var args = docAttr.ArgumentList?.Arguments;
+ if (args == null || args.Value.Count < 2)
+ {
+ return node;
+ }
+
+ // Get description (2nd argument - index 1)
+ var description = GetStringLiteral(args.Value[1].Expression);
+
+ if (string.IsNullOrWhiteSpace(description))
+ {
+ return node;
+ }
+
+ // Get args array (3rd argument - index 2) if present
+ string[]? paramDescriptions = null;
+ if (args.Value.Count >= 3)
+ {
+ paramDescriptions = GetStringArray(args.Value[2].Expression);
+ }
+
+ // Generate XML documentation comment text
+ var commentText = GenerateXmlCommentText(node, description, paramDescriptions);
+
+ // Add the comment as leading trivia
+ var existingLeadingTrivia = node.GetLeadingTrivia();
+ var newTrivia = SyntaxFactory.ParseLeadingTrivia(commentText);
+ var combinedTrivia = existingLeadingTrivia.AddRange(newTrivia);
+
+ Modified = true;
+ return node.WithLeadingTrivia(combinedTrivia);
+ }
+
+ private bool HasXmlDocumentation(SyntaxNode node)
+ {
+ return node.GetLeadingTrivia()
+ .Any(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) ||
+ t.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia));
+ }
+
+ private string? GetStringLiteral(ExpressionSyntax expr)
+ {
+ if (expr is LiteralExpressionSyntax literal &&
+ literal.IsKind(SyntaxKind.StringLiteralExpression))
+ {
+ return literal.Token.ValueText;
+ }
+
+ // Handle verbatim strings @"..."
+ var text = expr.ToString();
+ if (text.StartsWith("@\"") && text.EndsWith("\""))
+ {
+ return text.Substring(2, text.Length - 3);
+ }
+
+ return null;
+ }
+
+ private string[]? GetStringArray(ExpressionSyntax expr)
+ {
+ if (expr.ToString() == "null")
+ {
+ return null;
+ }
+
+ if (expr is ImplicitArrayCreationExpressionSyntax implicitArray)
+ {
+ return implicitArray.Initializer.Expressions
+ .Select(e => GetStringLiteral(e))
+ .Where(s => s != null)
+ .ToArray()!;
+ }
+
+ if (expr is ArrayCreationExpressionSyntax arrayExpr && arrayExpr.Initializer != null)
+ {
+ return arrayExpr.Initializer.Expressions
+ .Select(e => GetStringLiteral(e))
+ .Where(s => s != null)
+ .ToArray()!;
+ }
+
+ return null;
+ }
+
+ private string GenerateXmlCommentText(
+ MemberDeclarationSyntax node,
+ string description,
+ string[]? paramDescriptions)
+ {
+ var sb = new StringBuilder();
+ var indent = GetIndentation(node);
+
+ // Add
+ sb.AppendLine($"{indent}/// ");
+ sb.AppendLine($"{indent}/// {description}");
+ sb.AppendLine($"{indent}/// ");
+
+ // Add for generic types
+ if (node is TypeDeclarationSyntax typeDecl && typeDecl.TypeParameterList != null)
+ {
+ foreach (var typeParam in typeDecl.TypeParameterList.Parameters)
+ {
+ sb.AppendLine($"{indent}/// The {typeParam.Identifier.Text} type parameter");
+ }
+ }
+
+ // Add tags for methods
+ if (node is MethodDeclarationSyntax method)
+ {
+ var parameters = method.ParameterList.Parameters;
+ foreach (var param in parameters)
+ {
+ var paramDesc = GetParameterDescription(param.Identifier.Text, paramDescriptions);
+ sb.AppendLine($"{indent}/// {paramDesc}");
+ }
+
+ // Add if not void
+ if (!method.ReturnType.ToString().Contains("void"))
+ {
+ sb.AppendLine($"{indent}/// The return value");
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private string GetIndentation(SyntaxNode node)
+ {
+ var trivia = node.GetLeadingTrivia();
+ var whitespace = trivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
+ return whitespace.ToString();
+ }
+
+ private string GetParameterDescription(string paramName, string[]? paramDescriptions)
+ {
+ if (paramDescriptions == null)
+ {
+ return $"The {paramName} parameter";
+ }
+
+ foreach (var desc in paramDescriptions)
+ {
+ if (desc.StartsWith($"{paramName}:", StringComparison.OrdinalIgnoreCase))
+ {
+ return desc.Substring(paramName.Length + 1).Trim();
+ }
+ }
+
+ return $"The {paramName} parameter";
+ }
+}
diff --git a/OmegaLeo.HelperLib/Helpers/BenchmarkUtility.cs b/OmegaLeo.HelperLib/Helpers/BenchmarkUtility.cs
index 460c347..6635a40 100755
--- a/OmegaLeo.HelperLib/Helpers/BenchmarkUtility.cs
+++ b/OmegaLeo.HelperLib/Helpers/BenchmarkUtility.cs
@@ -5,7 +5,10 @@ using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Helpers
{
- [Documentation(nameof(BenchmarkUtility), "Utility class for benchmarking code execution time.", null, @"```csharp
+ ///
+ /// Utility class for benchmarking code execution time.
+ ///
+[Documentation(nameof(BenchmarkUtility), "Utility class for benchmarking code execution time.", null, @"```csharp
BenchmarkUtility.Start(""MyBenchmark"");
// Code to benchmark
BenchmarkUtility.Stop(""MyBenchmark"");
@@ -20,7 +23,12 @@ var results = BenchmarkUtility.GetResults(""MyBenchmark"");
private static Dictionary> _benchmarks = new Dictionary>();
private static Dictionary _stopwatches = new Dictionary();
- [Documentation("GetStopwatch", "Retrieves or creates a Stopwatch instance for the given key.", null, null)]
+ ///
+ /// Retrieves or creates a Stopwatch instance for the given key.
+ ///
+ /// The key parameter
+ /// The return value
+[Documentation("GetStopwatch", "Retrieves or creates a Stopwatch instance for the given key.", null, null)]
private static Stopwatch GetStopwatch(string key)
{
Stopwatch stopwatch;
@@ -34,7 +42,12 @@ var results = BenchmarkUtility.GetResults(""MyBenchmark"");
return stopwatch;
}
- [Documentation("Record", "Records the execution time of the provided action and returns the elapsed time in milliseconds.", null, @"```csharp
+ ///
+ /// Records the execution time of the provided action and returns the elapsed time in milliseconds.
+ ///
+ /// The actionToRecord parameter
+ /// The return value
+[Documentation("Record", "Records the execution time of the provided action and returns the elapsed time in milliseconds.", null, @"```csharp
var time = BenchmarkUtility.Record(() =>
{
// Code to benchmark
@@ -53,7 +66,13 @@ Console.WriteLine($""Elapsed time: {time} ms"");
return stopwatch.ElapsedMilliseconds;
}
- [Documentation("RecordAndSaveToResults", "Records the execution time of the provided action, saves it under the given key, and returns the elapsed time in milliseconds.", null, @"```csharp
+ ///
+ /// Records the execution time of the provided action, saves it under the given key, and returns the elapsed time in milliseconds.
+ ///
+ /// The key parameter
+ /// The actionToRecord parameter
+ /// The return value
+[Documentation("RecordAndSaveToResults", "Records the execution time of the provided action, saves it under the given key, and returns the elapsed time in milliseconds.", null, @"```csharp
var time = BenchmarkUtility.RecordAndSaveToResults(""MyBenchmark"", () =>
{
// Code to benchmark
@@ -85,7 +104,11 @@ Console.WriteLine($""Average time: {averageTime} ms"");
return stopwatch.ElapsedMilliseconds;
}
- [Documentation("Start", "Starts or restarts the stopwatch for the given key.", null, null)]
+ ///
+ /// Starts or restarts the stopwatch for the given key.
+ ///
+ /// The key parameter
+[Documentation("Start", "Starts or restarts the stopwatch for the given key.", null, null)]
///
/// Starts or restarts the stopwatch for the given key.
///
@@ -95,7 +118,11 @@ Console.WriteLine($""Average time: {averageTime} ms"");
GetStopwatch(key).Restart();
}
- [Documentation("Stop", "Stops the stopwatch for the given key and records the elapsed time.", null, null)]
+ ///
+ /// Stops the stopwatch for the given key and records the elapsed time.
+ ///
+ /// The key parameter
+[Documentation("Stop", "Stops the stopwatch for the given key and records the elapsed time.", null, null)]
///
/// Stops the stopwatch for the given key and records the elapsed time.
///
@@ -111,7 +138,12 @@ Console.WriteLine($""Average time: {averageTime} ms"");
_benchmarks[key].Add(GetStopwatch(key).ElapsedMilliseconds);
}
- [Documentation("GetResults", "Retrieves the list of recorded times for the given key.", null, null)]
+ ///
+ /// Retrieves the list of recorded times for the given key.
+ ///
+ /// The key parameter
+ /// The return value
+[Documentation("GetResults", "Retrieves the list of recorded times for the given key.", null, null)]
///
/// Retrieves the list of recorded times for the given key.
///
@@ -122,13 +154,20 @@ Console.WriteLine($""Average time: {averageTime} ms"");
return _benchmarks.ContainsKey(key) ? _benchmarks[key] : new List();
}
- [Documentation("ClearResults", "Clears all recorded benchmark results.", null, null)]
+ ///
+ /// Clears all recorded benchmark results.
+ ///
+[Documentation("ClearResults", "Clears all recorded benchmark results.", null, null)]
public static void ClearResults()
{
_benchmarks.Clear();
}
- [Documentation("GetAllResults", "Retrieves all recorded benchmark results.", null, null)]
+ ///
+ /// Retrieves all recorded benchmark results.
+ ///
+ /// The return value
+[Documentation("GetAllResults", "Retrieves all recorded benchmark results.", null, null)]
public static Dictionary> GetAllResults()
{
return new Dictionary>(_benchmarks);