From b55cbef3caa71b4eb8409c4f31b5fd39899342c4 Mon Sep 17 00:00:00 2001 From: Marc Simkin Date: Sun, 17 May 2026 16:04:10 -0400 Subject: [PATCH 1/2] =?UTF-8?q?-=20Fixed=20an=20issue=20where=20an=20empty?= =?UTF-8?q?=20class=20item=20was=20being=20generated.=20This=20caused=20a?= =?UTF-8?q?=20Mermaid=20parse=20error=20when=20attempting=20to=20render=20?= =?UTF-8?q?the=20diagram.=20=20=20(see=20`MermaidGenerator.Generate()`=20?= =?UTF-8?q?=E2=80=94=20`foreach=20(var=20@class=20...)`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - In `Program.cs`, updated `tnOption` to allow multiple arguments per token. This means the `--type-names` option no longer needs to be repeated for each type name. Instead of: ``` --type-names class1 --type-names class2 ``` you can now write: ``` --type-names class1 class2 ``` - Added a new `--diagram-direction` option to control diagram orientation. This required adding a new file: `ClassGraph/DiagramDirection.cs`. Valid diagram directions are: - `TB` — Top to Bottom - `BT` — Bottom to Top - `LR` — Left to Right - `RL` — Right to Left The default is **Top to Bottom**. - Added a new `--high-level-only` option to generate diagrams that include only class names, excluding properties, methods, and other members. This is useful when including diagrams in documentation where only high‑level relationships are needed. --- src/ClassGraph/ClassGraph.cs | 2 +- src/ClassGraph/DiagramDirection.cs | 9 +++++++ src/ClassGraph/IDiagramGenerator.cs | 4 +-- src/ClassGraph/IGraphBuilder.cs | 2 +- src/ClassGraph/MermaidGenerator.cs | 18 +++++++++---- src/ClassGraph/SourceGraphBuilder.cs | 2 +- src/MermaidClassDiagramGenerator/Program.cs | 27 +++++++++++++++---- .../Properties/launchSettings.json | 8 ++++++ 8 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 src/ClassGraph/DiagramDirection.cs create mode 100644 src/MermaidClassDiagramGenerator/Properties/launchSettings.json diff --git a/src/ClassGraph/ClassGraph.cs b/src/ClassGraph/ClassGraph.cs index 3badb06..9e30a1e 100644 --- a/src/ClassGraph/ClassGraph.cs +++ b/src/ClassGraph/ClassGraph.cs @@ -1,4 +1,4 @@ -namespace DiagramGenerator.ClassGraph; +namespace ClassGraph; public class Graph { diff --git a/src/ClassGraph/DiagramDirection.cs b/src/ClassGraph/DiagramDirection.cs new file mode 100644 index 0000000..3b46fd7 --- /dev/null +++ b/src/ClassGraph/DiagramDirection.cs @@ -0,0 +1,9 @@ +namespace ClassGraph; + +public enum DiagramDirection +{ + TB, // Top to Bottom + BT, // Bottom to Top + LR, // Left to Right + RL // Right to Left +} \ No newline at end of file diff --git a/src/ClassGraph/IDiagramGenerator.cs b/src/ClassGraph/IDiagramGenerator.cs index 103995c..deec951 100644 --- a/src/ClassGraph/IDiagramGenerator.cs +++ b/src/ClassGraph/IDiagramGenerator.cs @@ -1,6 +1,6 @@ -namespace DiagramGenerator.ClassGraph; +namespace ClassGraph; public interface IDiagramGenerator { - string Generate(Graph graph); + string Generate(Graph graph, bool highLevelOnly, DiagramDirection diagramDirection); } \ No newline at end of file diff --git a/src/ClassGraph/IGraphBuilder.cs b/src/ClassGraph/IGraphBuilder.cs index 561fd9c..a339c85 100644 --- a/src/ClassGraph/IGraphBuilder.cs +++ b/src/ClassGraph/IGraphBuilder.cs @@ -1,4 +1,4 @@ -namespace DiagramGenerator.ClassGraph; +namespace ClassGraph; public interface IGraphBuilder { diff --git a/src/ClassGraph/MermaidGenerator.cs b/src/ClassGraph/MermaidGenerator.cs index 593fa2f..1287a49 100644 --- a/src/ClassGraph/MermaidGenerator.cs +++ b/src/ClassGraph/MermaidGenerator.cs @@ -1,9 +1,10 @@ -namespace DiagramGenerator.ClassGraph; +namespace ClassGraph; public class MermaidGenerator : IDiagramGenerator { private static string MDFrame = @"```mermaid classDiagram + direction {2} {0} {1} @@ -15,10 +16,13 @@ public class MermaidGenerator : IDiagramGenerator { {1}{2} }}"; - public string Generate(Graph graph) { + public string Generate(Graph graph, bool highLevelOnly, DiagramDirection diagramDirection) { var allClass = new List(); foreach (var @class in graph.Classes) { - var classString = GenerateClass(@class); + // fixed an issue where a class with empty name would generate invalid + // Mermaid syntax and break the entire diagram - just skip any classes with empty/whitespace names + if (string.IsNullOrWhiteSpace(@class.Name)) continue; + var classString = GenerateClass(@class, highLevelOnly); allClass.Add(classString); } @@ -40,10 +44,10 @@ public string Generate(Graph graph) { sections += "\r\n\r\n" + relationSection; } - return string.Format(MDFrame, sections, string.Empty); + return string.Format(MDFrame, sections, string.Empty, diagramDirection.ToString()); } - private string GenerateClass(Class @class) { + private string GenerateClass(Class @class, bool highLevelOnly) { var lines = new List(); // Add type annotation (<>, <>, etc.) as first line inside class block @@ -52,6 +56,10 @@ private string GenerateClass(Class @class) { lines.Add($" {typeAnnotation}"); } + if (highLevelOnly) { + return string.Format(ClassFrame, @class.Name, string.Empty, string.Empty); + } + // For enums, add enum values instead of properties/methods if (@class.Kind == TypeKind.Enum) { foreach (var enumValue in @class.EnumValues) { diff --git a/src/ClassGraph/SourceGraphBuilder.cs b/src/ClassGraph/SourceGraphBuilder.cs index a8f05ab..6403164 100644 --- a/src/ClassGraph/SourceGraphBuilder.cs +++ b/src/ClassGraph/SourceGraphBuilder.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace DiagramGenerator.ClassGraph; +namespace ClassGraph; public class SourceGraphBuilder : IGraphBuilder { diff --git a/src/MermaidClassDiagramGenerator/Program.cs b/src/MermaidClassDiagramGenerator/Program.cs index 3de68ce..f244e92 100644 --- a/src/MermaidClassDiagramGenerator/Program.cs +++ b/src/MermaidClassDiagramGenerator/Program.cs @@ -1,5 +1,5 @@ using System.CommandLine; -using DiagramGenerator.ClassGraph; +using ClassGraph; var outputOption = new Option( aliases: new[] { "--output", "-o" }, @@ -20,7 +20,8 @@ var tnOption = new Option>( aliases: new[] { "--type-names", "-t" }, description: "Specific classes to include.", - getDefaultValue: () => new List()); + getDefaultValue: () => new List()) + { AllowMultipleArgumentsPerToken = true }; var ignoreDependencyOption = new Option( name: "--ignore-dependency", @@ -44,6 +45,16 @@ description: "Additional patterns to exclude from file search (e.g., 'Migrations', 'Generated').", getDefaultValue: () => new List()); +var highLevelOnlyOption = new Option( + aliases: new[] { "--high-level-only", "-h" }, + description: "Exclude all properties, methods, etc from generated diagram.", + getDefaultValue: () => false); + +var diagramDirectionOption = new Option( + aliases: new[] { "--diagram-direction", "-d" }, + description: "The direction that diagram should be generated (TB: Top to Bottom (default), BT: Bottom to Top, LR: Left to Right, RL: Right to Left)", + getDefaultValue: () => DiagramDirection.TB); + var rootCommand = new RootCommand("Generate mermaid.js class-diagram from C# source code files."); rootCommand.AddOption(outputOption); rootCommand.AddOption(nsOption); @@ -54,6 +65,8 @@ rootCommand.AddOption(visibilityOption); rootCommand.AddOption(verboseOption); rootCommand.AddOption(excludePatternsOption); +rootCommand.AddOption(highLevelOnlyOption); +rootCommand.AddOption(diagramDirectionOption); rootCommand.SetHandler((context) => { @@ -66,8 +79,10 @@ var visLevel = context.ParseResult.GetValueForOption(visibilityOption); var verbose = context.ParseResult.GetValueForOption(verboseOption); var excludePatterns = context.ParseResult.GetValueForOption(excludePatternsOption); + var highLevelOnly = context.ParseResult.GetValueForOption(highLevelOnlyOption); + var diagramDirection = context.ParseResult.GetValueForOption(diagramDirectionOption); - Execute(output!, ns!, inputPath!, tns!, ignoreDep, excludeSys, visLevel!, verbose, excludePatterns!); + Execute(output!, ns!, inputPath!, tns!, ignoreDep, excludeSys, visLevel!, verbose, excludePatterns!, highLevelOnly, diagramDirection); }); return await rootCommand.InvokeAsync(args); @@ -80,7 +95,9 @@ static void Execute(FileInfo outputFile, bool excludeSystemTypes, string visibilityLevel, bool verbose, - IList excludePatterns) + IList excludePatterns, + bool highLevelOnly, + DiagramDirection diagramDirection) { try { @@ -142,7 +159,7 @@ static void Execute(FileInfo outputFile, // 3. Generate Mermaid diagram var generator = new MermaidGenerator(); - var text = generator.Generate(graph); + var text = generator.Generate(graph, highLevelOnly, diagramDirection); // 4. Write output File.WriteAllText(outputFile.FullName, text); diff --git a/src/MermaidClassDiagramGenerator/Properties/launchSettings.json b/src/MermaidClassDiagramGenerator/Properties/launchSettings.json new file mode 100644 index 0000000..6d91b28 --- /dev/null +++ b/src/MermaidClassDiagramGenerator/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "MermaidClassDiagramGenerator": { + "commandName": "Project", + "commandLineArgs": "--path D:\\Work\\BN\\ProductDataSystemsGitRepos\\Tools\\Messaging\\src --output D:\\Work\\BN\\ProductDataSystemsGitRepos\\Tools\\Messaging\\docs\\diagrams\\MessagingDiagram.md --high-level-only --diagram-direction LR --type-names IDelivery MessagingException RabbitMqDelivery RabbitMqMessage IRabbitMqDelivery UnexpectedBinaryMessageException InvalidRoutingKeyException LegacyBackboneMessage IMessage --verbose" + } + } +} \ No newline at end of file From 3f76175f4f771b1f40403abc9511e735166e0d7f Mon Sep 17 00:00:00 2001 From: Marc Simkin Date: Sun, 17 May 2026 16:18:43 -0400 Subject: [PATCH 2/2] applied the coderabbit suggestions. (a) removed the -h alias so as to not conflict with the default system.commandline behavior; (b) added a string empty check to the graph.Relations iteration. --- src/ClassGraph/MermaidGenerator.cs | 6 +++++- src/MermaidClassDiagramGenerator/Program.cs | 2 +- .../Properties/launchSettings.json | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ClassGraph/MermaidGenerator.cs b/src/ClassGraph/MermaidGenerator.cs index 1287a49..de834ea 100644 --- a/src/ClassGraph/MermaidGenerator.cs +++ b/src/ClassGraph/MermaidGenerator.cs @@ -28,7 +28,11 @@ public string Generate(Graph graph, bool highLevelOnly, DiagramDirection diagram var allRelation = new List(); foreach (var relation in graph.Relations) { - var relationString = GenerateRelation(relation); + // if either end of the relation has empty/whitespace name, skip it to avoid breaking Mermaid syntax + if (string.IsNullOrWhiteSpace(relation.From?.Name) || + string.IsNullOrWhiteSpace(relation.To?.Name)) continue; + + var relationString = GenerateRelation(relation); allRelation.Add(relationString); } diff --git a/src/MermaidClassDiagramGenerator/Program.cs b/src/MermaidClassDiagramGenerator/Program.cs index f244e92..87200b2 100644 --- a/src/MermaidClassDiagramGenerator/Program.cs +++ b/src/MermaidClassDiagramGenerator/Program.cs @@ -46,7 +46,7 @@ getDefaultValue: () => new List()); var highLevelOnlyOption = new Option( - aliases: new[] { "--high-level-only", "-h" }, + aliases: new[] { "--high-level-only" }, description: "Exclude all properties, methods, etc from generated diagram.", getDefaultValue: () => false); diff --git a/src/MermaidClassDiagramGenerator/Properties/launchSettings.json b/src/MermaidClassDiagramGenerator/Properties/launchSettings.json index 6d91b28..018b357 100644 --- a/src/MermaidClassDiagramGenerator/Properties/launchSettings.json +++ b/src/MermaidClassDiagramGenerator/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "MermaidClassDiagramGenerator": { - "commandName": "Project", - "commandLineArgs": "--path D:\\Work\\BN\\ProductDataSystemsGitRepos\\Tools\\Messaging\\src --output D:\\Work\\BN\\ProductDataSystemsGitRepos\\Tools\\Messaging\\docs\\diagrams\\MessagingDiagram.md --high-level-only --diagram-direction LR --type-names IDelivery MessagingException RabbitMqDelivery RabbitMqMessage IRabbitMqDelivery UnexpectedBinaryMessageException InvalidRoutingKeyException LegacyBackboneMessage IMessage --verbose" + "commandName": "Project" } } } \ No newline at end of file