From 90f9abcb4574efef3b974998e3874cfb285f80fd Mon Sep 17 00:00:00 2001 From: akravtsova Date: Thu, 12 Dec 2024 15:51:55 +0600 Subject: [PATCH 1/9] + new tag, works with -dir: -addPackageTags --- README.md | 10 +-- .../ClassDiagramGenerator.cs | 70 +++++++++++++++++-- .../NamespaceNameText.cs | 24 +++++++ .../RelationshipCollection.cs | 9 +++ src/PlantUmlClassDiagramGenerator/Program.cs | 19 ++++- .../ClassDiagramGeneratorTest.cs | 1 + 6 files changed, 122 insertions(+), 11 deletions(-) rename src/PlantUmlClassDiagramGenerator.Library/{ => ClassDiagramGenerator}/ClassDiagramGenerator.cs (89%) create mode 100644 src/PlantUmlClassDiagramGenerator.Library/NamespaceNameText.cs diff --git a/README.md b/README.md index 4acaf4b..eac715a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ This is a generator to create a class-diagram of PlantUML from the C# source cod **README.md Version revision history** -| Version | Commit | Comment | -| ------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| 1.1 | [e73b4fe](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/e73b4feed9cd261271eb990a9c859f53536e8d7c) | Add "-excludeUmlBeginEndTags" option | +| Version | Commit | Comment | +|---------| ------------------------------------------------------------ |--------------------------------------------------------------------------------------------------------------| +| 1.2 | [df40b74](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/df40b748875c36c22e0f1267ddf4c93cd928b6b9) | Add "-addPackageTags" option | +| 1.1 | [e73b4fe](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/e73b4feed9cd261271eb990a9c859f53536e8d7c) | Add "-excludeUmlBeginEndTags" option | | 1.0 | [70bb820](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/70bb8202f7f489aa2d85ce9c25c58121c8f63aed) | Because the README.md for other languages is not always updated at the same time, a version number is needed | ## Roslyn Source Generator @@ -39,13 +40,14 @@ dotnet tool install --global PlantUmlClassDiagramGenerator Run the "puml-gen" command. ```bat -puml-gen InputPath [OutputPath] [-dir] [-public | -ignore IgnoreAccessibilities] [-excludePaths ExcludePathList] [-createAssociation] +puml-gen InputPath [OutputPath] [-dir] [-addPackageTags] [-public | -ignore IgnoreAccessibilities] [-excludePaths ExcludePathList] [-createAssociation] ``` - InputPath: (Required) Sets a input source file or directory name. - OutputPath: (Optional) Sets a output file or directory name. If you omit this option, plantuml files are outputted to same directory as the input files. - -dir: (Optional) Specify when InputPath and OutputPath are directory names. +- -addPackageTags: (Optional) If there is "-dir" tag, then program adds "package" tags and puts all relations in the end of include.puml - -public: (Optional) If specified, only public accessibility members are output. - -ignore: (Optional) Specify the accessibility of members to ignore, with a comma separated list. - -excludePaths: (Optional) Specify the exclude file and directory. diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs similarity index 89% rename from src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs rename to src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs index c9ff164..5902e8f 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGenerator.Library; +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; public class ClassDiagramGenerator( TextWriter writer, @@ -16,18 +16,20 @@ public class ClassDiagramGenerator( Accessibilities ignoreMemberAccessibilities = Accessibilities.None, bool createAssociation = true, bool attributeRequired = false, - bool excludeUmlBeginEndTags = false) : CSharpSyntaxWalker + bool excludeUmlBeginEndTags = false, + bool addPackageTags = false) : CSharpSyntaxWalker { private readonly HashSet types = []; private readonly List additionalTypeDeclarationNodes = []; private readonly Accessibilities ignoreMemberAccessibilities = ignoreMemberAccessibilities; - private readonly RelationshipCollection relationships = new(); + public readonly RelationshipCollection relationships = new(); private readonly TextWriter writer = writer; private readonly string indent = indent; private int nestingDepth = 0; private readonly bool createAssociation = createAssociation; private readonly bool attributeRequired = attributeRequired; private readonly bool excludeUmlBeginEndTags = excludeUmlBeginEndTags; + private readonly bool addPackageTags = addPackageTags; private readonly Dictionary escapeDictionary = new() { {@"(?[^{]){(?{[^{])", "${before}{${after}"}, @@ -45,7 +47,24 @@ public void GenerateInternal(SyntaxNode root) { Visit(root); GenerateAdditionalTypeDeclarations(); - GenerateRelationships(); + if (!this.addPackageTags) + GenerateRelationships(); + } + + public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) + { + if (this.addPackageTags) + VisitFileScopedNamespaceDeclaration(node, () => base.VisitFileScopedNamespaceDeclaration(node)); + else + base.VisitFileScopedNamespaceDeclaration(node); + } + + public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + if (this.addPackageTags) + VisitNamespaceDeclaration(node, () => base.VisitNamespaceDeclaration(node)); + else + base.VisitNamespaceDeclaration(node); } public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) @@ -371,7 +390,7 @@ private void WriteLine(string line) private bool SkipInnerTypeDeclaration(SyntaxNode node) { if (nestingDepth <= 0) return false; - + if (nestingDepth == 1 && addPackageTags) return false; additionalTypeDeclarationNodes.Add(node); return true; } @@ -411,6 +430,47 @@ private void GenerateRelationships() WriteLine(relationship.ToString()); } } + + public static string[] GenerateRelationships(RelationshipCollection relationshipCollection) + { + List strings = new List(); + foreach (var relationship in relationshipCollection) + { + strings.AddRange(relationshipCollection.Select(r => r.ToString())); + } + + return strings.ToArray(); + } + + private void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node, Action visitBase) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + var typeName = NamespaceNameText.From(node); + + WriteLine($"package \"{typeName.Identifier}\" {{"); + nestingDepth++; + visitBase(); + nestingDepth--; + WriteLine("}"); + } + + private void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node, Action visitBase) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + var typeName = NamespaceNameText.From(node); + + WriteLine($"package \"{typeName.Identifier}\"{{"); + nestingDepth++; + visitBase(); + nestingDepth--; + WriteLine("}"); + } private void VisitTypeDeclaration(TypeDeclarationSyntax node, Action visitBase) { diff --git a/src/PlantUmlClassDiagramGenerator.Library/NamespaceNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/NamespaceNameText.cs new file mode 100644 index 0000000..51158f1 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/NamespaceNameText.cs @@ -0,0 +1,24 @@ +namespace PlantUmlClassDiagramGenerator.Library; + +using Microsoft.CodeAnalysis.CSharp.Syntax; + +public class NamespaceNameText +{ + public string Identifier { get; set; } + + public static NamespaceNameText From(FileScopedNamespaceDeclarationSyntax syntax) + { + return new NamespaceNameText + { + Identifier = syntax.Name.ToString() + }; + } + + public static NamespaceNameText From(NamespaceDeclarationSyntax syntax) + { + return new NamespaceNameText + { + Identifier = syntax.Name.ToString() + }; + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index bce54e4..4e922fd 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using PlantUmlClassDiagramGenerator.Attributes; @@ -10,6 +11,14 @@ public class RelationshipCollection : IEnumerable { private readonly IList items = new List(); + public void AddAll(RelationshipCollection collection) + { + foreach (var c in collection) + { + items.Add(c); + } + } + public void AddInheritanceFrom(TypeDeclarationSyntax syntax) { if (syntax.BaseList == null) return; diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index 864acbf..a9336a9 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -7,6 +7,7 @@ using System.Text; using PlantUmlClassDiagramGenerator.Library; using System.Runtime.InteropServices; +using PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; namespace PlantUmlClassDiagramGenerator; @@ -27,7 +28,8 @@ enum OptionType ["-createAssociation"] = OptionType.Switch, ["-allInOne"] = OptionType.Switch, ["-attributeRequired"] = OptionType.Switch, - ["-excludeUmlBeginEndTags"] = OptionType.Switch + ["-excludeUmlBeginEndTags"] = OptionType.Switch, + ["-addPackageTags"] = OptionType.Switch }; static int Main(string[] args) @@ -153,6 +155,7 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter var error = false; var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); + RelationshipCollection relationships = new(); foreach (var inputFile in filesToProcess) { Console.WriteLine($"Processing \"{inputFile}\"..."); @@ -177,8 +180,10 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter ignoreAcc, parameters.ContainsKey("-createAssociation"), parameters.ContainsKey("-attributeRequired"), - excludeUmlBeginEndTags); + excludeUmlBeginEndTags, + parameters.ContainsKey("-addPackageTags")); gen.Generate(root); + relationships.AddAll(gen.relationships); } if (parameters.ContainsKey("-allInOne")) @@ -205,6 +210,16 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter error = true; } } + + if (parameters.ContainsKey("-addPackageTags")) + { + var lines = ClassDiagramGenerator.GenerateRelationships(relationships); + foreach (string line in lines.Distinct()) + { + includeRefs.AppendLine(line); + } + } + if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@enduml"); File.WriteAllText(CombinePath(outputRoot, "include.puml"), includeRefs.ToString()); diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index d37caa2..eaf022f 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -3,6 +3,7 @@ using System.Text; using System.IO; using PlantUmlClassDiagramGenerator.Library; +using PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; using Xunit; using Xunit.Abstractions; From 5c898c525a4fba42d1decd6355c3d00c07931900 Mon Sep 17 00:00:00 2001 From: akravtsova Date: Thu, 12 Dec 2024 16:26:09 +0600 Subject: [PATCH 2/9] refactor ClassDiagramGenerator --- .../AdditionalTypeGenerator.cs | 35 ++ .../ClassDiagramGenerator.cs | 415 +----------------- .../ConstructorVisitor.cs | 30 ++ .../ClassDiagramGenerator/EnumVisitor.cs | 32 ++ .../ClassDiagramGenerator/EventVisitor.cs | 21 + .../ClassDiagramGenerator/FieldVisitor.cs | 57 +++ .../ClassDiagramGenerator/MethodVisitor.cs | 31 ++ .../ClassDiagramGenerator/NamespaceVisitor.cs | 54 +++ .../ClassDiagramGenerator/PropertyVisitor.cs | 64 +++ .../ClassDiagramGenerator/RecordVisitor.cs | 84 ++++ .../RelationshipGenerator.cs | 24 + .../ClassDiagramGenerator/StructVisitor.cs | 31 ++ .../ClassDiagramGenerator/TypeVisitor.cs | 48 ++ 13 files changed, 513 insertions(+), 413 deletions(-) create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/AdditionalTypeGenerator.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ConstructorVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EnumVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EventVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/MethodVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/NamespaceVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RecordVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/StructVisitor.cs create mode 100644 src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/TypeVisitor.cs diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/AdditionalTypeGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/AdditionalTypeGenerator.cs new file mode 100644 index 0000000..17eba29 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/AdditionalTypeGenerator.cs @@ -0,0 +1,35 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + private void GenerateAdditionalTypeDeclarations() + { + for (int i = 0; i < additionalTypeDeclarationNodes.Count; i++) + { + SyntaxNode node = additionalTypeDeclarationNodes[i]; + if (node is GenericNameSyntax genericNode) + { + if (createAssociation) + { + GenerateAdditionalGenericTypeDeclaration(genericNode); + } + continue; + } + Visit(node); + } + } + + private void GenerateAdditionalGenericTypeDeclaration(GenericNameSyntax genericNode) + { + var typename = TypeNameText.From(genericNode); + if (!types.Contains(typename.Identifier)) + { + WriteLine($"class {typename.Identifier}{typename.TypeArguments} {{"); + WriteLine("}"); + types.Add(typename.Identifier); + } + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs index 5902e8f..1744ef6 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -10,7 +8,7 @@ namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; -public class ClassDiagramGenerator( +public partial class ClassDiagramGenerator( TextWriter writer, string indent, Accessibilities ignoreMemberAccessibilities = Accessibilities.None, @@ -51,272 +49,6 @@ public void GenerateInternal(SyntaxNode root) GenerateRelationships(); } - public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) - { - if (this.addPackageTags) - VisitFileScopedNamespaceDeclaration(node, () => base.VisitFileScopedNamespaceDeclaration(node)); - else - base.VisitFileScopedNamespaceDeclaration(node); - } - - public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) - { - if (this.addPackageTags) - VisitNamespaceDeclaration(node, () => base.VisitNamespaceDeclaration(node)); - else - base.VisitNamespaceDeclaration(node); - } - - public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) - { - VisitTypeDeclaration(node, () => base.VisitInterfaceDeclaration(node)); - } - - public override void VisitClassDeclaration(ClassDeclarationSyntax node) - { - VisitTypeDeclaration(node, () => base.VisitClassDeclaration(node)); - } - - public override void VisitRecordDeclaration(RecordDeclarationSyntax node) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - relationships.AddInnerclassRelationFrom(node); - relationships.AddInheritanceFrom(node); - var modifiers = GetTypeModifiersText(node.Modifiers); - var abstractKeyword = (node.Modifiers.Any(SyntaxKind.AbstractKeyword) ? "abstract " : ""); - - var typeName = TypeNameText.From(node); - var name = typeName.Identifier; - var typeParam = typeName.TypeArguments; - var type = $"{name}{typeParam}"; - var typeParams = typeParam.TrimStart('<').TrimEnd('>').Split([','], StringSplitOptions.RemoveEmptyEntries); - types.Add(name); - - var typeKeyword = (node.Kind() == SyntaxKind.RecordStructDeclaration) ? "struct" : "class"; - WriteLine($"{abstractKeyword}{typeKeyword} {type} {modifiers}<> {{"); - - nestingDepth++; - var parameters = node.ParameterList?.Parameters ?? Enumerable.Empty(); - foreach (var parameter in parameters) - { - VisitRecordParameter(node, type, typeParams, parameter); - } - base.VisitRecordDeclaration(node); - nestingDepth--; - - WriteLine("}"); - } - - private void VisitRecordParameter(RecordDeclarationSyntax node, string type, string[] typeParams, ParameterSyntax parameter) - { - var parameterType = parameter.Type; - var isTypeParameterProp = typeParams.Contains(parameterType.ToString()); - var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); - if (associationAttrSyntax is not null) - { - var associationAttr = CreateAssociationAttribute(associationAttrSyntax); - relationships.AddAssociationFrom(node, parameter, associationAttr); - } - else if (!createAssociation - || parameter.AttributeLists.HasIgnoreAssociationAttribute() - || parameterType.GetType() == typeof(PredefinedTypeSyntax) - || parameterType.GetType() == typeof(NullableTypeSyntax) - || isTypeParameterProp) - { - // ParameterList-Property: always public - var parameterModifiers = "+ "; - var parameterName = parameter.Identifier.ToString(); - - // ParameterList-Property always have get and init accessor - var accessorStr = "<> <>"; - - var useLiteralInit = parameter.Default?.Value is not null; - var initValue = useLiteralInit - ? (" = " + escapeDictionary.Aggregate(parameter.Default.Value.ToString(), - (n, e) => Regex.Replace(n, e.Key, e.Value))) - : ""; - WriteLine($"{parameterModifiers}{parameterName} : {parameterType} {accessorStr}{initValue}"); - } - else - { - if (type.GetType() == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(parameterType); - } - relationships.AddAssociationFrom(parameter, node); - } - } - - public override void VisitStructDeclaration(StructDeclarationSyntax node) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - relationships.AddInnerclassRelationFrom(node); - relationships.AddInheritanceFrom(node); - - var typeName = TypeNameText.From(node); - var name = typeName.Identifier; - var typeParam = typeName.TypeArguments; - var type = $"{name}{typeParam}"; - - types.Add(name); - - WriteLine($"struct {type} {{"); - - nestingDepth++; - base.VisitStructDeclaration(node); - nestingDepth--; - - WriteLine("}"); - } - - public override void VisitEnumDeclaration(EnumDeclarationSyntax node) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - relationships.AddInnerclassRelationFrom(node); - - var type = $"{node.Identifier}"; - - types.Add(type); - - WriteLine($"{node.EnumKeyword} {type} {{"); - - nestingDepth++; - base.VisitEnumDeclaration(node); - nestingDepth--; - - WriteLine("}"); - } - - public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) - { - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (IsIgnoreMember(node.Modifiers)) { return; } - foreach (var parameter in node.ParameterList?.Parameters) - { - var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); - if (associationAttrSyntax is not null) - { - var associationAttr = CreateAssociationAttribute(associationAttrSyntax); - relationships.AddAssociationFrom(node, parameter, associationAttr); - } - } - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var name = node.Identifier.ToString(); - var args = node.ParameterList.Parameters.Select(p => $"{p.Identifier}:{p.Type}"); - - WriteLine($"{modifiers}{name}({string.Join(", ", args)})"); - } - - public override void VisitFieldDeclaration(FieldDeclarationSyntax node) - { - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (IsIgnoreMember(node.Modifiers)) { return; } - - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var type = node.Declaration.Type; - var variables = node.Declaration.Variables; - var parentClass = (node.Parent as TypeDeclarationSyntax); - var isTypeParameterField = parentClass?.TypeParameterList?.Parameters - .Any(t => t.Identifier.Text == type.ToString()) ?? false; - - foreach (var field in variables) - { - Type fieldType = type.GetType(); - var associationAttrSyntax = node.AttributeLists.GetAssociationAttributeSyntax(); - if (associationAttrSyntax is not null) - { - var associationAttr = CreateAssociationAttribute(associationAttrSyntax); - relationships.AddAssociationFrom(node, associationAttr); - } - else if (!createAssociation - || node.AttributeLists.HasIgnoreAssociationAttribute() - || fieldType == typeof(PredefinedTypeSyntax) - || fieldType == typeof(NullableTypeSyntax) - || isTypeParameterField) - { - var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; - var initValue = useLiteralInit - ? (" = " + escapeDictionary.Aggregate(field.Initializer.Value.ToString(), - (f, e) => Regex.Replace(f, e.Key, e.Value))) - : ""; - WriteLine($"{modifiers}{field.Identifier} : {type}{initValue}"); - } - else - { - if (fieldType == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(type); - } - relationships.AddAssociationFrom(node, field); - } - } - } - - public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) - { - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (IsIgnoreMember(node.Modifiers)) { return; } - - var type = node.Type; - - var parentClass = (node.Parent as TypeDeclarationSyntax); - var isTypeParameterProp = parentClass?.TypeParameterList?.Parameters - .Any(t => t.Identifier.Text == type.ToString()) ?? false; - - var typeIgnoringNullable = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : type; - - var associationAttrSyntax = node.AttributeLists.GetAssociationAttributeSyntax(); - if (associationAttrSyntax is not null) - { - var associationAttr = CreateAssociationAttribute(associationAttrSyntax); - relationships.AddAssociationFrom(node, associationAttr); - } - else if (!createAssociation - || node.AttributeLists.HasIgnoreAssociationAttribute() - || typeIgnoringNullable is PredefinedTypeSyntax - || isTypeParameterProp) - { - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var name = node.Identifier.ToString(); - //Property does not have an accessor is an expression-bodied property. (get only) - var accessorStr = "<>"; - if (node.AccessorList != null) - { - var accessor = node.AccessorList.Accessors - .Where(x => !x.Modifiers.Select(y => y.Kind()).Contains(SyntaxKind.PrivateKeyword)) - .Select(x => $"<<{(x.Modifiers.ToString() == "" ? "" : (x.Modifiers.ToString() + " "))}{x.Keyword}>>"); - accessorStr = string.Join(" ", accessor); - } - var useLiteralInit = node.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; - var initValue = useLiteralInit - ? (" = " + escapeDictionary.Aggregate(node.Initializer.Value.ToString(), - (n, e) => Regex.Replace(n, e.Key, e.Value))) - : ""; - - WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); - } - else - { - if (type.GetType() == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(type); - } - relationships.AddAssociationFrom(node, typeIgnoringNullable); - } - } - private static PlantUmlAssociationAttribute CreateAssociationAttribute(AttributeSyntax associationAttribute) { var attributeProps = associationAttribute.ArgumentList.Arguments.Select(arg => new @@ -334,44 +66,7 @@ private static PlantUmlAssociationAttribute CreateAssociationAttribute(Attribute }; } - public override void VisitMethodDeclaration(MethodDeclarationSyntax node) - { - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (IsIgnoreMember(node.Modifiers)) { return; } - foreach (var parameter in node.ParameterList?.Parameters) - { - var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); - if (associationAttrSyntax is not null) - { - var associationAttr = CreateAssociationAttribute(associationAttrSyntax); - relationships.AddAssociationFrom(node, parameter, associationAttr); - } - } - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var name = node.Identifier.ToString(); - var returnType = node.ReturnType.ToString(); - var args = node.ParameterList.Parameters.Select(p => $"{p.Identifier}:{p.Type}"); - - WriteLine($"{modifiers}{name}({string.Join(", ", args)}) : {returnType}"); - } - - public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) - { - WriteLine($"{node.Identifier}{node.EqualsValue},"); - } - - public override void VisitEventFieldDeclaration(EventFieldDeclarationSyntax node) - { - if (IsIgnoreMember(node.Modifiers)) { return; } - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var name = string.Join(",", node.Declaration.Variables.Select(v => v.Identifier)); - var typeName = node.Declaration.Type.ToString(); - - WriteLine($"{modifiers} <<{node.EventKeyword}>> {name} : {typeName} "); - } public override void VisitGenericName(GenericNameSyntax node) { @@ -395,112 +90,6 @@ private bool SkipInnerTypeDeclaration(SyntaxNode node) return true; } - private void GenerateAdditionalTypeDeclarations() - { - for (int i = 0; i < additionalTypeDeclarationNodes.Count; i++) - { - SyntaxNode node = additionalTypeDeclarationNodes[i]; - if (node is GenericNameSyntax genericNode) - { - if (createAssociation) - { - GenerateAdditionalGenericTypeDeclaration(genericNode); - } - continue; - } - Visit(node); - } - } - - private void GenerateAdditionalGenericTypeDeclaration(GenericNameSyntax genericNode) - { - var typename = TypeNameText.From(genericNode); - if (!types.Contains(typename.Identifier)) - { - WriteLine($"class {typename.Identifier}{typename.TypeArguments} {{"); - WriteLine("}"); - types.Add(typename.Identifier); - } - } - - private void GenerateRelationships() - { - foreach (var relationship in relationships) - { - WriteLine(relationship.ToString()); - } - } - - public static string[] GenerateRelationships(RelationshipCollection relationshipCollection) - { - List strings = new List(); - foreach (var relationship in relationshipCollection) - { - strings.AddRange(relationshipCollection.Select(r => r.ToString())); - } - - return strings.ToArray(); - } - - private void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node, Action visitBase) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - var typeName = NamespaceNameText.From(node); - - WriteLine($"package \"{typeName.Identifier}\" {{"); - nestingDepth++; - visitBase(); - nestingDepth--; - WriteLine("}"); - } - - private void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node, Action visitBase) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - var typeName = NamespaceNameText.From(node); - - WriteLine($"package \"{typeName.Identifier}\"{{"); - nestingDepth++; - visitBase(); - nestingDepth--; - WriteLine("}"); - } - - private void VisitTypeDeclaration(TypeDeclarationSyntax node, Action visitBase) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } - - relationships.AddInnerclassRelationFrom(node); - relationships.AddInheritanceFrom(node); - - var modifiers = GetTypeModifiersText(node.Modifiers); - var keyword = (node.Modifiers.Any(SyntaxKind.AbstractKeyword) ? "abstract " : "") - + node.Keyword.ToString(); - - var typeName = TypeNameText.From(node); - var name = typeName.Identifier; - var typeParam = typeName.TypeArguments; - var type = $"{name}{typeParam}"; - - types.Add(name); - - WriteLine($"{keyword} {type} {modifiers}{{"); - - nestingDepth++; - visitBase(); - nestingDepth--; - - WriteLine("}"); - } - private static string GetTypeModifiersText(SyntaxTokenList modifiers) { var tokens = modifiers.Select(token => diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ConstructorVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ConstructorVisitor.cs new file mode 100644 index 0000000..ef96b71 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ConstructorVisitor.cs @@ -0,0 +1,30 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + foreach (var parameter in node.ParameterList?.Parameters) + { + var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) + { + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, parameter, associationAttr); + } + } + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var name = node.Identifier.ToString(); + var args = node.ParameterList.Parameters.Select(p => $"{p.Identifier}:{p.Type}"); + + WriteLine($"{modifiers}{name}({string.Join(", ", args)})"); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EnumVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EnumVisitor.cs new file mode 100644 index 0000000..cba15e7 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EnumVisitor.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitEnumDeclaration(EnumDeclarationSyntax node) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + relationships.AddInnerclassRelationFrom(node); + + var type = $"{node.Identifier}"; + + types.Add(type); + + WriteLine($"{node.EnumKeyword} {type} {{"); + + nestingDepth++; + base.VisitEnumDeclaration(node); + nestingDepth--; + + WriteLine("}"); + } + + public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) + { + WriteLine($"{node.Identifier}{node.EqualsValue},"); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EventVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EventVisitor.cs new file mode 100644 index 0000000..b1c8cf0 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/EventVisitor.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitEventFieldDeclaration(EventFieldDeclarationSyntax node) + { + if (IsIgnoreMember(node.Modifiers)) { return; } + + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var name = string.Join(",", node.Declaration.Variables.Select(v => v.Identifier)); + var typeName = node.Declaration.Type.ToString(); + + WriteLine($"{modifiers} <<{node.EventKeyword}>> {name} : {typeName} "); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs new file mode 100644 index 0000000..a950ba4 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitFieldDeclaration(FieldDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var type = node.Declaration.Type; + var variables = node.Declaration.Variables; + var parentClass = (node.Parent as TypeDeclarationSyntax); + var isTypeParameterField = parentClass?.TypeParameterList?.Parameters + .Any(t => t.Identifier.Text == type.ToString()) ?? false; + + foreach (var field in variables) + { + Type fieldType = type.GetType(); + var associationAttrSyntax = node.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) + { + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, associationAttr); + } + else if (!createAssociation + || node.AttributeLists.HasIgnoreAssociationAttribute() + || fieldType == typeof(PredefinedTypeSyntax) + || fieldType == typeof(NullableTypeSyntax) + || isTypeParameterField) + { + var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; + var initValue = useLiteralInit + ? (" = " + escapeDictionary.Aggregate(field.Initializer.Value.ToString(), + (f, e) => Regex.Replace(f, e.Key, e.Value))) + : ""; + WriteLine($"{modifiers}{field.Identifier} : {type}{initValue}"); + } + else + { + if (fieldType == typeof(GenericNameSyntax)) + { + additionalTypeDeclarationNodes.Add(type); + } + relationships.AddAssociationFrom(node, field); + } + } + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/MethodVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/MethodVisitor.cs new file mode 100644 index 0000000..1169d6d --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/MethodVisitor.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitMethodDeclaration(MethodDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + foreach (var parameter in node.ParameterList?.Parameters) + { + var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) + { + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, parameter, associationAttr); + } + } + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var name = node.Identifier.ToString(); + var returnType = node.ReturnType.ToString(); + var args = node.ParameterList.Parameters.Select(p => $"{p.Identifier}:{p.Type}"); + + WriteLine($"{modifiers}{name}({string.Join(", ", args)}) : {returnType}"); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/NamespaceVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/NamespaceVisitor.cs new file mode 100644 index 0000000..4ebef85 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/NamespaceVisitor.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) + { + if (this.addPackageTags) + VisitFileScopedNamespaceDeclaration(node, () => base.VisitFileScopedNamespaceDeclaration(node)); + else + base.VisitFileScopedNamespaceDeclaration(node); + } + + public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + if (this.addPackageTags) + VisitNamespaceDeclaration(node, () => base.VisitNamespaceDeclaration(node)); + else + base.VisitNamespaceDeclaration(node); + } + + private void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node, Action visitBase) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + var typeName = NamespaceNameText.From(node); + + WriteLine($"package \"{typeName.Identifier}\" {{"); + nestingDepth++; + visitBase(); + nestingDepth--; + WriteLine("}"); + } + + private void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node, Action visitBase) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + var typeName = NamespaceNameText.From(node); + + WriteLine($"package \"{typeName.Identifier}\"{{"); + nestingDepth++; + visitBase(); + nestingDepth--; + WriteLine("}"); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs new file mode 100644 index 0000000..f1dc978 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + + var type = node.Type; + + var parentClass = (node.Parent as TypeDeclarationSyntax); + var isTypeParameterProp = parentClass?.TypeParameterList?.Parameters + .Any(t => t.Identifier.Text == type.ToString()) ?? false; + + var typeIgnoringNullable = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : type; + + var associationAttrSyntax = node.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) + { + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, associationAttr); + } + else if (!createAssociation + || node.AttributeLists.HasIgnoreAssociationAttribute() + || typeIgnoringNullable is PredefinedTypeSyntax + || isTypeParameterProp) + { + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var name = node.Identifier.ToString(); + //Property does not have an accessor is an expression-bodied property. (get only) + var accessorStr = "<>"; + if (node.AccessorList != null) + { + var accessor = node.AccessorList.Accessors + .Where(x => !x.Modifiers.Select(y => y.Kind()).Contains(SyntaxKind.PrivateKeyword)) + .Select(x => $"<<{(x.Modifiers.ToString() == "" ? "" : (x.Modifiers.ToString() + " "))}{x.Keyword}>>"); + accessorStr = string.Join(" ", accessor); + } + var useLiteralInit = node.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; + var initValue = useLiteralInit + ? (" = " + escapeDictionary.Aggregate(node.Initializer.Value.ToString(), + (n, e) => Regex.Replace(n, e.Key, e.Value))) + : ""; + + WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); + } + else + { + if (type.GetType() == typeof(GenericNameSyntax)) + { + additionalTypeDeclarationNodes.Add(type); + } + relationships.AddAssociationFrom(node, typeIgnoringNullable); + } + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RecordVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RecordVisitor.cs new file mode 100644 index 0000000..3ea47ba --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RecordVisitor.cs @@ -0,0 +1,84 @@ +using System.Linq; +using System; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitRecordDeclaration(RecordDeclarationSyntax node) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + relationships.AddInnerclassRelationFrom(node); + relationships.AddInheritanceFrom(node); + var modifiers = GetTypeModifiersText(node.Modifiers); + var abstractKeyword = (node.Modifiers.Any(SyntaxKind.AbstractKeyword) ? "abstract " : ""); + + var typeName = TypeNameText.From(node); + var name = typeName.Identifier; + var typeParam = typeName.TypeArguments; + var type = $"{name}{typeParam}"; + var typeParams = typeParam.TrimStart('<').TrimEnd('>').Split([','], StringSplitOptions.RemoveEmptyEntries); + types.Add(name); + + var typeKeyword = (node.Kind() == SyntaxKind.RecordStructDeclaration) ? "struct" : "class"; + WriteLine($"{abstractKeyword}{typeKeyword} {type} {modifiers}<> {{"); + + nestingDepth++; + var parameters = node.ParameterList?.Parameters ?? Enumerable.Empty(); + foreach (var parameter in parameters) + { + VisitRecordParameter(node, type, typeParams, parameter); + } + base.VisitRecordDeclaration(node); + nestingDepth--; + + WriteLine("}"); + } + + private void VisitRecordParameter(RecordDeclarationSyntax node, string type, string[] typeParams, ParameterSyntax parameter) + { + var parameterType = parameter.Type; + var isTypeParameterProp = typeParams.Contains(parameterType.ToString()); + var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) + { + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, parameter, associationAttr); + } + else if (!createAssociation + || parameter.AttributeLists.HasIgnoreAssociationAttribute() + || parameterType.GetType() == typeof(PredefinedTypeSyntax) + || parameterType.GetType() == typeof(NullableTypeSyntax) + || isTypeParameterProp) + { + // ParameterList-Property: always public + var parameterModifiers = "+ "; + var parameterName = parameter.Identifier.ToString(); + + // ParameterList-Property always have get and init accessor + var accessorStr = "<> <>"; + + var useLiteralInit = parameter.Default?.Value is not null; + var initValue = useLiteralInit + ? (" = " + escapeDictionary.Aggregate(parameter.Default.Value.ToString(), + (n, e) => Regex.Replace(n, e.Key, e.Value))) + : ""; + WriteLine($"{parameterModifiers}{parameterName} : {parameterType} {accessorStr}{initValue}"); + } + else + { + if (type.GetType() == typeof(GenericNameSyntax)) + { + additionalTypeDeclarationNodes.Add(parameterType); + } + relationships.AddAssociationFrom(parameter, node); + } + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs new file mode 100644 index 0000000..bac3b78 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + private void GenerateRelationships() + { + foreach (var relationship in relationships) + { + WriteLine(relationship.ToString()); + } + } + + public static string[] GenerateRelationships(RelationshipCollection relationshipCollection) + { + List strings = new List(); + strings.AddRange(relationshipCollection.Select(r => r.ToString())); + + return strings.ToArray(); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/StructVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/StructVisitor.cs new file mode 100644 index 0000000..0408097 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/StructVisitor.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitStructDeclaration(StructDeclarationSyntax node) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + relationships.AddInnerclassRelationFrom(node); + relationships.AddInheritanceFrom(node); + + var typeName = TypeNameText.From(node); + var name = typeName.Identifier; + var typeParam = typeName.TypeArguments; + var type = $"{name}{typeParam}"; + + types.Add(name); + + WriteLine($"struct {type} {{"); + + nestingDepth++; + base.VisitStructDeclaration(node); + nestingDepth--; + + WriteLine("}"); + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/TypeVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/TypeVisitor.cs new file mode 100644 index 0000000..7086486 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/TypeVisitor.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +public partial class ClassDiagramGenerator +{ + public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) + { + VisitTypeDeclaration(node, () => base.VisitInterfaceDeclaration(node)); + } + + public override void VisitClassDeclaration(ClassDeclarationSyntax node) + { + VisitTypeDeclaration(node, () => base.VisitClassDeclaration(node)); + } + + private void VisitTypeDeclaration(TypeDeclarationSyntax node, Action visitBase) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } + + relationships.AddInnerclassRelationFrom(node); + relationships.AddInheritanceFrom(node); + + var modifiers = GetTypeModifiersText(node.Modifiers); + var keyword = (node.Modifiers.Any(SyntaxKind.AbstractKeyword) ? "abstract " : "") + + node.Keyword.ToString(); + + var typeName = TypeNameText.From(node); + var name = typeName.Identifier; + var typeParam = typeName.TypeArguments; + var type = $"{name}{typeParam}"; + + types.Add(name); + + WriteLine($"{keyword} {type} {modifiers}{{"); + + nestingDepth++; + visitBase(); + nestingDepth--; + + WriteLine("}"); + } +} \ No newline at end of file From cd9eed2539f9bac73410e6b6f1566ce979429f6f Mon Sep 17 00:00:00 2001 From: akravtsova Date: Thu, 12 Dec 2024 16:41:06 +0600 Subject: [PATCH 3/9] refactor Program.cs --- .../Generator/IPlantUmlGenerator.cs | 34 +++ .../Generator/PlantUmlFromDirGenerator.cs | 139 +++++++++++ .../Generator/PlantUmlFromFileGenerator.cs | 66 +++++ src/PlantUmlClassDiagramGenerator/Program.cs | 227 +----------------- 4 files changed, 249 insertions(+), 217 deletions(-) create mode 100644 src/PlantUmlClassDiagramGenerator/Generator/IPlantUmlGenerator.cs create mode 100644 src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs create mode 100644 src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs diff --git a/src/PlantUmlClassDiagramGenerator/Generator/IPlantUmlGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/IPlantUmlGenerator.cs new file mode 100644 index 0000000..02f9ff5 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator/Generator/IPlantUmlGenerator.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using PlantUmlClassDiagramGenerator.Library; + +namespace PlantUmlClassDiagramGenerator.Generator; + +public interface IPlantUmlGenerator +{ + public bool GeneratePlantUml(Dictionary parameters); + + public static Accessibilities GetIgnoreAccessibilities(Dictionary parameters) + { + var ignoreAcc = Accessibilities.None; + if (parameters.ContainsKey("-public")) + { + ignoreAcc = Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal; + } + else if (parameters.TryGetValue("-ignore", out string value)) + { + var ignoreItems = value.Split(','); + foreach (var item in ignoreItems) + { + if (Enum.TryParse(item, true, out Accessibilities acc)) + { + ignoreAcc |= acc; + } + } + } + return ignoreAcc; + } + + +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs new file mode 100644 index 0000000..b5223a4 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using PlantUmlClassDiagramGenerator.Library; +using PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +namespace PlantUmlClassDiagramGenerator.Generator; + +public class PlantUmlFromDirGenerator: IPlantUmlGenerator +{ + public bool GeneratePlantUml(Dictionary parameters) + { + var inputRoot = parameters["in"]; + if (!Directory.Exists(inputRoot)) + { + Console.WriteLine($"Directory \"{inputRoot}\" does not exist."); + return false; + } + + // Use GetFullPath to fully support relative paths. + var outputRoot = Path.GetFullPath(inputRoot); + if (parameters.TryGetValue("out", out string outValue)) + { + outputRoot = outValue; + try + { + Directory.CreateDirectory(outputRoot); + } + catch (Exception e) + { + Console.WriteLine(e); + return false; + } + } + + var excludePaths = new List(); + var pumlexclude = PathHelper.CombinePath(inputRoot, ".pumlexclude"); + if (File.Exists(pumlexclude)) + { + excludePaths = File + .ReadAllLines(pumlexclude) + .Where(path => !string.IsNullOrWhiteSpace(path)) + .Select(path => path.Trim()) + .ToList(); + } + if (parameters.TryGetValue("-excludePaths", out string excludePathValue)) + { + var splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; + excludePaths.AddRange(excludePathValue.Split(',', splitOptions)); + } + + var excludeUmlBeginEndTags = parameters.ContainsKey("-excludeUmlBeginEndTags"); + var files = Directory.EnumerateFiles(inputRoot, "*.cs", SearchOption.AllDirectories); + + var includeRefs = new StringBuilder(); + if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@startuml"); + + var error = false; + var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); + RelationshipCollection relationships = new(); + foreach (var inputFile in filesToProcess) + { + Console.WriteLine($"Processing \"{inputFile}\"..."); + try + { + var outputDir = PathHelper.CombinePath(outputRoot, Path.GetDirectoryName(inputFile).Replace(inputRoot, "")); + Directory.CreateDirectory(outputDir); + var outputFile = PathHelper.CombinePath(outputDir, Path.GetFileNameWithoutExtension(inputFile) + ".puml"); + + using (var stream = new FileStream(inputFile, FileMode.Open, FileAccess.Read)) + { + var tree = CSharpSyntaxTree.ParseText(SourceText.From(stream)); + var root = tree.GetRoot(); + Accessibilities ignoreAcc = IPlantUmlGenerator.GetIgnoreAccessibilities(parameters); + + using var filestream = new FileStream(outputFile, FileMode.Create, FileAccess.Write); + using var writer = new StreamWriter(filestream); + var gen = new ClassDiagramGenerator( + writer, + " ", + ignoreAcc, + parameters.ContainsKey("-createAssociation"), + parameters.ContainsKey("-attributeRequired"), + excludeUmlBeginEndTags, + parameters.ContainsKey("-addPackageTags")); + gen.Generate(root); + relationships.AddAll(gen.relationships); + } + + if (parameters.ContainsKey("-allInOne")) + { + var lines = File.ReadAllLines(outputFile); + if (!excludeUmlBeginEndTags) + { + lines = lines.Skip(1).SkipLast(1).ToArray(); + } + foreach (string line in lines) + { + includeRefs.AppendLine(line); + } + } + else + { + var newRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @".\" : @"."; + includeRefs.AppendLine("!include " + outputFile.Replace(outputRoot, newRoot)); + } + } + catch (Exception e) + { + Console.WriteLine(e); + error = true; + } + } + + if (parameters.ContainsKey("-addPackageTags")) + { + var lines = ClassDiagramGenerator.GenerateRelationships(relationships); + foreach (string line in lines.Distinct()) + { + includeRefs.AppendLine(line); + } + } + + if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@enduml"); + File.WriteAllText(PathHelper.CombinePath(outputRoot, "include.puml"), includeRefs.ToString()); + + if (error) + { + Console.WriteLine("There were files that could not be processed."); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs new file mode 100644 index 0000000..dd27950 --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using PlantUmlClassDiagramGenerator.Library; +using PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; + +namespace PlantUmlClassDiagramGenerator.Generator; + +public class PlantUmlFromFileGenerator : IPlantUmlGenerator +{ + public bool GeneratePlantUml(Dictionary parameters) + { + var inputFileName = parameters["in"]; + if (!File.Exists(inputFileName)) + { + Console.WriteLine($"\"{inputFileName}\" does not exist."); + return false; + } + string outputFileName; + if (parameters.TryGetValue("out", out string value)) + { + outputFileName = value; + try + { + var outdir = Path.GetDirectoryName(outputFileName); + Directory.CreateDirectory(outdir); + } + catch (Exception e) + { + Console.WriteLine(e); + return false; + } + } + else + { + outputFileName = PathHelper.CombinePath(Path.GetDirectoryName(inputFileName), Path.GetFileNameWithoutExtension(inputFileName) + ".puml"); + } + + try + { + using var stream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read); + var tree = CSharpSyntaxTree.ParseText(SourceText.From(stream)); + var root = tree.GetRoot(); + Accessibilities ignoreAcc = IPlantUmlGenerator.GetIgnoreAccessibilities(parameters); + + using var filestream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write); + using var writer = new StreamWriter(filestream); + var gen = new ClassDiagramGenerator( + writer, + " ", + ignoreAcc, + parameters.ContainsKey("-createAssociation"), + parameters.ContainsKey("-attributeRequired"), + parameters.ContainsKey("-excludeUmlBeginEndTags")); + gen.Generate(root); + } + catch (Exception e) + { + Console.WriteLine(e); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index a9336a9..361e234 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -7,12 +7,15 @@ using System.Text; using PlantUmlClassDiagramGenerator.Library; using System.Runtime.InteropServices; +using PlantUmlClassDiagramGenerator.Generator; using PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; namespace PlantUmlClassDiagramGenerator; class Program { + private static IPlantUmlGenerator generator; + enum OptionType { Value, @@ -40,219 +43,15 @@ static int Main(string[] args) Console.WriteLine("Specify a source file name or directory name."); return -1; } - if (parameters.ContainsKey("-dir")) - { - if (!GeneratePlantUmlFromDir(parameters)) { return -1; } - } - else - { - if (!GeneratePlantUmlFromFile(parameters)) { return -1; } - } - return 0; - } - private static bool GeneratePlantUmlFromFile(Dictionary parameters) - { - var inputFileName = parameters["in"]; - if (!File.Exists(inputFileName)) - { - Console.WriteLine($"\"{inputFileName}\" does not exist."); - return false; - } - string outputFileName; - if (parameters.TryGetValue("out", out string value)) - { - outputFileName = value; - try - { - var outdir = Path.GetDirectoryName(outputFileName); - Directory.CreateDirectory(outdir); - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - } + if (parameters.ContainsKey("-dir")) + generator = new PlantUmlFromDirGenerator(); else - { - outputFileName = CombinePath(Path.GetDirectoryName(inputFileName), - Path.GetFileNameWithoutExtension(inputFileName) + ".puml"); - } - - try - { - using var stream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read); - var tree = CSharpSyntaxTree.ParseText(SourceText.From(stream)); - var root = tree.GetRoot(); - Accessibilities ignoreAcc = GetIgnoreAccessibilities(parameters); + generator = new PlantUmlFromFileGenerator(); - using var filestream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write); - using var writer = new StreamWriter(filestream); - var gen = new ClassDiagramGenerator( - writer, - " ", - ignoreAcc, - parameters.ContainsKey("-createAssociation"), - parameters.ContainsKey("-attributeRequired"), - parameters.ContainsKey("-excludeUmlBeginEndTags")); - gen.Generate(root); - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - return true; + return !generator.GeneratePlantUml(parameters) ? 1 : 0; } - private static bool GeneratePlantUmlFromDir(Dictionary parameters) - { - var inputRoot = parameters["in"]; - if (!Directory.Exists(inputRoot)) - { - Console.WriteLine($"Directory \"{inputRoot}\" does not exist."); - return false; - } - - // Use GetFullPath to fully support relative paths. - var outputRoot = Path.GetFullPath(inputRoot); - if (parameters.TryGetValue("out", out string outValue)) - { - outputRoot = outValue; - try - { - Directory.CreateDirectory(outputRoot); - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - } - - var excludePaths = new List(); - var pumlexclude = CombinePath(inputRoot, ".pumlexclude"); - if (File.Exists(pumlexclude)) - { - excludePaths = File - .ReadAllLines(pumlexclude) - .Where(path => !string.IsNullOrWhiteSpace(path)) - .Select(path => path.Trim()) - .ToList(); - } - if (parameters.TryGetValue("-excludePaths", out string excludePathValue)) - { - var splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; - excludePaths.AddRange(excludePathValue.Split(',', splitOptions)); - } - - var excludeUmlBeginEndTags = parameters.ContainsKey("-excludeUmlBeginEndTags"); - var files = Directory.EnumerateFiles(inputRoot, "*.cs", SearchOption.AllDirectories); - - var includeRefs = new StringBuilder(); - if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@startuml"); - - var error = false; - var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); - RelationshipCollection relationships = new(); - foreach (var inputFile in filesToProcess) - { - Console.WriteLine($"Processing \"{inputFile}\"..."); - try - { - var outputDir = CombinePath(outputRoot, Path.GetDirectoryName(inputFile).Replace(inputRoot, "")); - Directory.CreateDirectory(outputDir); - var outputFile = CombinePath(outputDir, - Path.GetFileNameWithoutExtension(inputFile) + ".puml"); - - using (var stream = new FileStream(inputFile, FileMode.Open, FileAccess.Read)) - { - var tree = CSharpSyntaxTree.ParseText(SourceText.From(stream)); - var root = tree.GetRoot(); - Accessibilities ignoreAcc = GetIgnoreAccessibilities(parameters); - - using var filestream = new FileStream(outputFile, FileMode.Create, FileAccess.Write); - using var writer = new StreamWriter(filestream); - var gen = new ClassDiagramGenerator( - writer, - " ", - ignoreAcc, - parameters.ContainsKey("-createAssociation"), - parameters.ContainsKey("-attributeRequired"), - excludeUmlBeginEndTags, - parameters.ContainsKey("-addPackageTags")); - gen.Generate(root); - relationships.AddAll(gen.relationships); - } - - if (parameters.ContainsKey("-allInOne")) - { - var lines = File.ReadAllLines(outputFile); - if (!excludeUmlBeginEndTags) - { - lines = lines.Skip(1).SkipLast(1).ToArray(); - } - foreach (string line in lines) - { - includeRefs.AppendLine(line); - } - } - else - { - var newRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @".\" : @"."; - includeRefs.AppendLine("!include " + outputFile.Replace(outputRoot, newRoot)); - } - } - catch (Exception e) - { - Console.WriteLine(e); - error = true; - } - } - - if (parameters.ContainsKey("-addPackageTags")) - { - var lines = ClassDiagramGenerator.GenerateRelationships(relationships); - foreach (string line in lines.Distinct()) - { - includeRefs.AppendLine(line); - } - } - - if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@enduml"); - File.WriteAllText(CombinePath(outputRoot, "include.puml"), includeRefs.ToString()); - - if (error) - { - Console.WriteLine("There were files that could not be processed."); - return false; - } - return true; - } - - - private static Accessibilities GetIgnoreAccessibilities(Dictionary parameters) - { - var ignoreAcc = Accessibilities.None; - if (parameters.ContainsKey("-public")) - { - ignoreAcc = Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal; - } - else if (parameters.TryGetValue("-ignore", out string value)) - { - var ignoreItems = value.Split(','); - foreach (var item in ignoreItems) - { - if (Enum.TryParse(item, true, out Accessibilities acc)) - { - ignoreAcc |= acc; - } - } - } - return ignoreAcc; - } private static Dictionary MakeParameters(string[] args) { @@ -281,19 +80,13 @@ private static Dictionary MakeParameters(string[] args) } else { - if(!parameters.TryAdd("in", arg)) + if (!parameters.TryAdd("in", arg)) { parameters.TryAdd("out", arg); - } + } } - - } - return parameters; - } - private static string CombinePath(string first, string second) - { - return PathHelper.CombinePath(first, second); + return parameters; } } \ No newline at end of file From b464639c3e8027559f175fae1ac45945e56954be Mon Sep 17 00:00:00 2001 From: akravtsova Date: Thu, 12 Dec 2024 18:21:54 +0600 Subject: [PATCH 4/9] + new tag: removeSystemCollectionsAssociations --- .../ClassDiagramGenerator.cs | 4 +- .../ClassDiagramGenerator/PropertyVisitor.cs | 72 +++++++++++++------ .../SystemCollectionsTypes.cs | 35 +++++++++ .../Generator/PlantUmlFromDirGenerator.cs | 3 +- .../Generator/PlantUmlFromFileGenerator.cs | 4 +- src/PlantUmlClassDiagramGenerator/Program.cs | 3 +- 6 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs index 1744ef6..60bbe7d 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs @@ -15,7 +15,8 @@ public partial class ClassDiagramGenerator( bool createAssociation = true, bool attributeRequired = false, bool excludeUmlBeginEndTags = false, - bool addPackageTags = false) : CSharpSyntaxWalker + bool addPackageTags = false, + bool removeSystemCollectionsAssociations = false) : CSharpSyntaxWalker { private readonly HashSet types = []; private readonly List additionalTypeDeclarationNodes = []; @@ -28,6 +29,7 @@ public partial class ClassDiagramGenerator( private readonly bool attributeRequired = attributeRequired; private readonly bool excludeUmlBeginEndTags = excludeUmlBeginEndTags; private readonly bool addPackageTags = addPackageTags; + private readonly bool removeSystemCollectionsAssociations = removeSystemCollectionsAssociations; private readonly Dictionary escapeDictionary = new() { {@"(?[^{]){(?{[^{])", "${before}{${after}"}, diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs index f1dc978..9147efb 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs @@ -1,8 +1,10 @@ +using System; using System.Linq; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using PlantUmlClassDiagramGenerator.Attributes; namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; @@ -32,33 +34,59 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) || typeIgnoringNullable is PredefinedTypeSyntax || isTypeParameterProp) { - var modifiers = GetMemberModifiersText(node.Modifiers, - isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); - var name = node.Identifier.ToString(); - //Property does not have an accessor is an expression-bodied property. (get only) - var accessorStr = "<>"; - if (node.AccessorList != null) - { - var accessor = node.AccessorList.Accessors - .Where(x => !x.Modifiers.Select(y => y.Kind()).Contains(SyntaxKind.PrivateKeyword)) - .Select(x => $"<<{(x.Modifiers.ToString() == "" ? "" : (x.Modifiers.ToString() + " "))}{x.Keyword}>>"); - accessorStr = string.Join(" ", accessor); - } - var useLiteralInit = node.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; - var initValue = useLiteralInit - ? (" = " + escapeDictionary.Aggregate(node.Initializer.Value.ToString(), - (n, e) => Regex.Replace(n, e.Key, e.Value))) - : ""; - - WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); + FillAssociatedProperty(node, type); } else { if (type.GetType() == typeof(GenericNameSyntax)) { - additionalTypeDeclarationNodes.Add(type); - } - relationships.AddAssociationFrom(node, typeIgnoringNullable); + if (this.removeSystemCollectionsAssociations) + { + var t = node.Type.ToString().Split('<')[0]; + if (!Enum.TryParse(t, out SystemCollectionsTypes _)) + additionalTypeDeclarationNodes.Add(type); + else + { + FillAssociatedProperty(node, type); + var s = node.Type.ToString(); + relationships.AddAssociationFrom(node, new PlantUmlAssociationAttribute() + { + Association = "o--", + Name = s.Substring(s.IndexOf('<') + 1,s.LastIndexOf('>') - s.IndexOf('<') - 1) + }); + } + } + else + { + additionalTypeDeclarationNodes.Add(type); + relationships.AddAssociationFrom(node, typeIgnoringNullable); + } + } else + relationships.AddAssociationFrom(node, typeIgnoringNullable); } } + + private void FillAssociatedProperty(PropertyDeclarationSyntax node, TypeSyntax type) + { + var modifiers = GetMemberModifiersText(node.Modifiers, + isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); + var name = node.Identifier.ToString(); + //Property does not have an accessor is an expression-bodied property. (get only) + var accessorStr = "<>"; + if (node.AccessorList != null) + { + var accessor = node.AccessorList.Accessors + .Where(x => !x.Modifiers.Select(y => y.Kind()).Contains(SyntaxKind.PrivateKeyword)) + .Select(x => $"<<{(x.Modifiers.ToString() == "" ? "" : (x.Modifiers.ToString() + " "))}{x.Keyword}>>"); + accessorStr = string.Join(" ", accessor); + } + + var useLiteralInit = node.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; + var initValue = useLiteralInit + ? (" = " + escapeDictionary.Aggregate(node.Initializer.Value.ToString(), + (n, e) => Regex.Replace(n, e.Key, e.Value))) + : ""; + + WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); + } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs b/src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs new file mode 100644 index 0000000..187199e --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs @@ -0,0 +1,35 @@ +namespace PlantUmlClassDiagramGenerator.Library; + +public enum SystemCollectionsTypes +{ + // classes + ArrayList, + BitArray, + CaseInsensitiveComparer, + CaseInsensitiveHashCodeProvider, + CollectionBase, + Comparer, + DictionaryBase, + Hashtable, + Queue, + ReadOnlyCollectionBase, + SortedList, + Stack, + StructuralComparisons, + + // structs + DictionaryEntry, + + // interfaces + ICollection, + IComparer, + IDictionary, + IDictionaryEnumerator, + IEnumerable, + IEnumerator, + IEqualityComparer, + IHashCodeProvider, + IList, + IStructuralComparable, + IStructuralEquatable +} diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs index b5223a4..f826271 100644 --- a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs @@ -87,7 +87,8 @@ public bool GeneratePlantUml(Dictionary parameters) parameters.ContainsKey("-createAssociation"), parameters.ContainsKey("-attributeRequired"), excludeUmlBeginEndTags, - parameters.ContainsKey("-addPackageTags")); + parameters.ContainsKey("-addPackageTags"), + parameters.ContainsKey("-removeSystemCollectionsAssociations")); gen.Generate(root); relationships.AddAll(gen.relationships); } diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs index dd27950..e53165e 100644 --- a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs @@ -53,7 +53,9 @@ public bool GeneratePlantUml(Dictionary parameters) ignoreAcc, parameters.ContainsKey("-createAssociation"), parameters.ContainsKey("-attributeRequired"), - parameters.ContainsKey("-excludeUmlBeginEndTags")); + parameters.ContainsKey("-excludeUmlBeginEndTags"), + false, + parameters.ContainsKey("-removeSystemCollectionsAssociations")); gen.Generate(root); } catch (Exception e) diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index 361e234..dfef649 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -32,7 +32,8 @@ enum OptionType ["-allInOne"] = OptionType.Switch, ["-attributeRequired"] = OptionType.Switch, ["-excludeUmlBeginEndTags"] = OptionType.Switch, - ["-addPackageTags"] = OptionType.Switch + ["-addPackageTags"] = OptionType.Switch, + ["-removeSystemCollectionsAssociations"] = OptionType.Switch }; static int Main(string[] args) From d97e17911111f27906d2a5a3eeb3f8137ebdb979 Mon Sep 17 00:00:00 2001 From: akravtsova Date: Fri, 13 Dec 2024 09:57:08 +0600 Subject: [PATCH 5/9] * update readme, FieldVisitor, PropertyVisitor. No base types like int in <...>. Refactoring --- README.md | 30 ++++----- .../ClassDiagramGenerator.cs | 10 +++ .../ClassDiagramGenerator/FieldVisitor.cs | 63 +++++++++++++++---- .../ClassDiagramGenerator/PropertyVisitor.cs | 61 +++++++++++------- .../Enums/BaseTypes.cs | 26 ++++++++ .../{ => Enums}/SystemCollectionsTypes.cs | 2 +- 6 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 src/PlantUmlClassDiagramGenerator.Library/Enums/BaseTypes.cs rename src/PlantUmlClassDiagramGenerator.Library/{ => Enums}/SystemCollectionsTypes.cs (91%) diff --git a/README.md b/README.md index eac715a..729be41 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ This is a generator to create a class-diagram of PlantUML from the C# source cod **README.md Version revision history** -| Version | Commit | Comment | -|---------| ------------------------------------------------------------ |--------------------------------------------------------------------------------------------------------------| -| 1.2 | [df40b74](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/df40b748875c36c22e0f1267ddf4c93cd928b6b9) | Add "-addPackageTags" option | +| Version | Commit | Comment | +|---------|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| +| 1.3 | [b464639](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/b464639c3e8027559f175fae1ac45945e56954be) | Add "-removeSystemCollectionsAssociations" option | +| 1.2 | [cd9eed2](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/cd9eed2539f9bac73410e6b6f1566ce979429f6f) | Add "-addPackageTags" option | | 1.1 | [e73b4fe](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/e73b4feed9cd261271eb990a9c859f53536e8d7c) | Add "-excludeUmlBeginEndTags" option | | 1.0 | [70bb820](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/70bb8202f7f489aa2d85ce9c25c58121c8f63aed) | Because the README.md for other languages is not always updated at the same time, a version number is needed | @@ -43,20 +44,21 @@ Run the "puml-gen" command. puml-gen InputPath [OutputPath] [-dir] [-addPackageTags] [-public | -ignore IgnoreAccessibilities] [-excludePaths ExcludePathList] [-createAssociation] ``` -- InputPath: (Required) Sets a input source file or directory name. -- OutputPath: (Optional) Sets a output file or directory name. +- **InputPath:** (Required) Sets a input source file or directory name. +- **OutputPath:** (Optional) Sets a output file or directory name. If you omit this option, plantuml files are outputted to same directory as the input files. -- -dir: (Optional) Specify when InputPath and OutputPath are directory names. -- -addPackageTags: (Optional) If there is "-dir" tag, then program adds "package" tags and puts all relations in the end of include.puml -- -public: (Optional) If specified, only public accessibility members are output. -- -ignore: (Optional) Specify the accessibility of members to ignore, with a comma separated list. -- -excludePaths: (Optional) Specify the exclude file and directory. +- **-dir**: (Optional) Specify when InputPath and OutputPath are directory names. +- **-addPackageTags:** (Optional) If there is "-dir" tag, then program adds "package" tags and puts all relations in the end of include.puml. Relations will not be shown in other files +- **-removeSystemCollectionsAssociations**: (Optional) If there are properties or fields like "IList" and other SystemCollections, there will be no relation with IList, but relation with T will be shown, if it isn't base type (string, int ...) +- **-public:** (Optional) If specified, only public accessibility members are output. +- **-ignore:** (Optional) Specify the accessibility of members to ignore, with a comma separated list. +- **-excludePaths:** (Optional) Specify the exclude file and directory. Specifies a relative path from the "InputPath", with a comma separated list. To exclude multiple paths, which contain a specific folder name, preceed the name by "\*\*/". Example: "**/bin" -- -createAssociation: (Optional) Create object associations from references of fields and properites. -- -allInOne: (Optional) Only if -dir is set: copy the output of all diagrams to file include.puml (this allows a PlanUMLServer to render it). -- -attributeRequired: (Optional) When this switch is enabled, only types with "PlantUmlDiagramAttribute" in the type declaration will be output. -- -excludeUmlBeginEndTags: (Optional) When this switch is enabled, it will exclude the \"@startuml\" and \"@enduml\" tags from the puml file. +- **-createAssociation:** (Optional) Create object associations from references of fields and properites. +- **-allInOne:** (Optional) Only if -dir is set: copy the output of all diagrams to file include.puml (this allows a PlanUMLServer to render it). +- **-attributeRequired:** (Optional) When this switch is enabled, only types with "PlantUmlDiagramAttribute" in the type declaration will be output. +- **-excludeUmlBeginEndTags:** (Optional) When this switch is enabled, it will exclude the \"@startuml\" and \"@enduml\" tags from the puml file. examples ```bat diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs index 60bbe7d..b1d092e 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs @@ -193,4 +193,14 @@ private static bool HasAccessModifier(SyntaxTokenList modifiers) || token.IsKind(SyntaxKind.ProtectedKeyword) || token.IsKind(SyntaxKind.InternalKeyword)); } + + private static string CapitalizeFirstLetter(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + if (input.Length == 1) + return char.ToUpper(input[0]) + ""; + + return char.ToUpper(input[0]) + input.Substring(1); + } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs index a950ba4..7982a80 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/FieldVisitor.cs @@ -4,6 +4,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using PlantUmlClassDiagramGenerator.Attributes; +using PlantUmlClassDiagramGenerator.Library.Enums; namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; @@ -37,21 +39,60 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) || fieldType == typeof(NullableTypeSyntax) || isTypeParameterField) { - var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; - var initValue = useLiteralInit - ? (" = " + escapeDictionary.Aggregate(field.Initializer.Value.ToString(), - (f, e) => Regex.Replace(f, e.Key, e.Value))) - : ""; - WriteLine($"{modifiers}{field.Identifier} : {type}{initValue}"); + FillAssociatedField(field, modifiers, type); } else { - if (fieldType == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(type); - } - relationships.AddAssociationFrom(node, field); + if (type.GetType() == typeof(GenericNameSyntax)) + ProcessGenericType(node, type, field, modifiers); + else + relationships.AddAssociationFrom(node, field); } } } + + private void FillAssociatedField(VariableDeclaratorSyntax field, string modifiers, TypeSyntax type) + { + var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; + var initValue = useLiteralInit + ? (" = " + escapeDictionary.Aggregate(field.Initializer.Value.ToString(), + (f, e) => Regex.Replace(f, e.Key, e.Value))) + : ""; + WriteLine($"{modifiers}{field.Identifier} : {type}{initValue}"); + } + + private void ProcessGenericType(FieldDeclarationSyntax node, TypeSyntax type, VariableDeclaratorSyntax field, string modifiers) + { + if (this.removeSystemCollectionsAssociations) + { + ProcessWithoutSystemCollections(node, type, field, modifiers); + } + else + { + additionalTypeDeclarationNodes.Add(type); + relationships.AddAssociationFrom(node, field); + } + } + + private void ProcessWithoutSystemCollections(FieldDeclarationSyntax node, TypeSyntax type, VariableDeclaratorSyntax field, string modifiers) + { + var t = type.ToString().Split('<')[0]; + if (!Enum.TryParse(t, out SystemCollectionsTypes _)) + { + additionalTypeDeclarationNodes.Add(type); + relationships.AddAssociationFrom(node, field); + } + else + { + FillAssociatedField(field, modifiers, type); + var s = type.ToString().Split('<')[1]; + s = s.Remove(s.Length - 1); + if (!Enum.TryParse(CapitalizeFirstLetter(s), out BaseTypes _)) + relationships.AddAssociationFrom(node, new PlantUmlAssociationAttribute() + { + Association = "o--", + Name = s + }); + } + } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs index 9147efb..e8834ff 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using PlantUmlClassDiagramGenerator.Attributes; +using PlantUmlClassDiagramGenerator.Library.Enums; namespace PlantUmlClassDiagramGenerator.Library.ClassDiagramGenerator; @@ -39,33 +40,47 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) else { if (type.GetType() == typeof(GenericNameSyntax)) - { - if (this.removeSystemCollectionsAssociations) - { - var t = node.Type.ToString().Split('<')[0]; - if (!Enum.TryParse(t, out SystemCollectionsTypes _)) - additionalTypeDeclarationNodes.Add(type); - else - { - FillAssociatedProperty(node, type); - var s = node.Type.ToString(); - relationships.AddAssociationFrom(node, new PlantUmlAssociationAttribute() - { - Association = "o--", - Name = s.Substring(s.IndexOf('<') + 1,s.LastIndexOf('>') - s.IndexOf('<') - 1) - }); - } - } - else - { - additionalTypeDeclarationNodes.Add(type); - relationships.AddAssociationFrom(node, typeIgnoringNullable); - } - } else + ProcessGenericType(node, type, typeIgnoringNullable); + else relationships.AddAssociationFrom(node, typeIgnoringNullable); } } + private void ProcessGenericType(PropertyDeclarationSyntax node, TypeSyntax type, TypeSyntax typeIgnoringNullable) + { + if (this.removeSystemCollectionsAssociations) + { + ProcessWithoutSystemCollections(node, type, typeIgnoringNullable); + } + else + { + additionalTypeDeclarationNodes.Add(type); + relationships.AddAssociationFrom(node, typeIgnoringNullable); + } + } + + private void ProcessWithoutSystemCollections(PropertyDeclarationSyntax node, TypeSyntax type, TypeSyntax typeIgnoringNullable) + { + var t = node.Type.ToString().Split('<')[0]; + if (!Enum.TryParse(t, out SystemCollectionsTypes _)) + { + additionalTypeDeclarationNodes.Add(type); + relationships.AddAssociationFrom(node, typeIgnoringNullable); + } + else + { + FillAssociatedProperty(node, type); + var s = node.Type.ToString(); + s = s.Substring(s.IndexOf('<') + 1, s.LastIndexOf('>') - s.IndexOf('<') - 1); + if (!Enum.TryParse(CapitalizeFirstLetter(s), out BaseTypes _)) + relationships.AddAssociationFrom(node, new PlantUmlAssociationAttribute() + { + Association = "o--", + Name = s + }); + } + } + private void FillAssociatedProperty(PropertyDeclarationSyntax node, TypeSyntax type) { var modifiers = GetMemberModifiersText(node.Modifiers, diff --git a/src/PlantUmlClassDiagramGenerator.Library/Enums/BaseTypes.cs b/src/PlantUmlClassDiagramGenerator.Library/Enums/BaseTypes.cs new file mode 100644 index 0000000..0053b5b --- /dev/null +++ b/src/PlantUmlClassDiagramGenerator.Library/Enums/BaseTypes.cs @@ -0,0 +1,26 @@ +namespace PlantUmlClassDiagramGenerator.Library.Enums; + +public enum BaseTypes +{ + Int, + Int16, + Int32, + Int64, + Int128, + Long, + Short, + Byte, + Decimal, + Double, + Float, + Char, + Bool, + String, + StringBuilder, + Object, + Dynamic, + DateTime, + TimeSpan, + Guid +} + diff --git a/src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs b/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs similarity index 91% rename from src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs rename to src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs index 187199e..4640437 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/SystemCollectionsTypes.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs @@ -1,4 +1,4 @@ -namespace PlantUmlClassDiagramGenerator.Library; +namespace PlantUmlClassDiagramGenerator.Library.Enums; public enum SystemCollectionsTypes { From ebc52e88f4719d28948f881de67796fc71c88cbd Mon Sep 17 00:00:00 2001 From: akravtsova Date: Fri, 13 Dec 2024 14:15:21 +0600 Subject: [PATCH 6/9] + noGetSetForProperties --- README.md | 3 ++- .../ClassDiagramGenerator/ClassDiagramGenerator.cs | 4 +++- .../ClassDiagramGenerator/PropertyVisitor.cs | 5 ++++- .../Generator/PlantUmlFromDirGenerator.cs | 3 ++- .../Generator/PlantUmlFromFileGenerator.cs | 3 ++- src/PlantUmlClassDiagramGenerator/Program.cs | 3 ++- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 729be41..39ec32e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ This is a generator to create a class-diagram of PlantUML from the C# source cod | Version | Commit | Comment | |---------|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| -| 1.3 | [b464639](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/b464639c3e8027559f175fae1ac45945e56954be) | Add "-removeSystemCollectionsAssociations" option | +| 1.4 | [d97e179](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/d97e17911111f27906d2a5a3eeb3f8137ebdb979) | Add "-noGetSetForProperties" option | +| 1.3 | [d97e179](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/d97e17911111f27906d2a5a3eeb3f8137ebdb979) | Add "-removeSystemCollectionsAssociations" option | | 1.2 | [cd9eed2](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/cd9eed2539f9bac73410e6b6f1566ce979429f6f) | Add "-addPackageTags" option | | 1.1 | [e73b4fe](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/e73b4feed9cd261271eb990a9c859f53536e8d7c) | Add "-excludeUmlBeginEndTags" option | | 1.0 | [70bb820](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/70bb8202f7f489aa2d85ce9c25c58121c8f63aed) | Because the README.md for other languages is not always updated at the same time, a version number is needed | diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs index b1d092e..63b9d83 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/ClassDiagramGenerator.cs @@ -16,7 +16,8 @@ public partial class ClassDiagramGenerator( bool attributeRequired = false, bool excludeUmlBeginEndTags = false, bool addPackageTags = false, - bool removeSystemCollectionsAssociations = false) : CSharpSyntaxWalker + bool removeSystemCollectionsAssociations = false, + bool noGetSetForProperties = false) : CSharpSyntaxWalker { private readonly HashSet types = []; private readonly List additionalTypeDeclarationNodes = []; @@ -30,6 +31,7 @@ public partial class ClassDiagramGenerator( private readonly bool excludeUmlBeginEndTags = excludeUmlBeginEndTags; private readonly bool addPackageTags = addPackageTags; private readonly bool removeSystemCollectionsAssociations = removeSystemCollectionsAssociations; + private readonly bool noGetSetForProperties = noGetSetForProperties; private readonly Dictionary escapeDictionary = new() { {@"(?[^{]){(?{[^{])", "${before}{${after}"}, diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs index e8834ff..b41a629 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/PropertyVisitor.cs @@ -102,6 +102,9 @@ private void FillAssociatedProperty(PropertyDeclarationSyntax node, TypeSyntax t (n, e) => Regex.Replace(n, e.Key, e.Value))) : ""; - WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); + if (noGetSetForProperties) + WriteLine($"{modifiers}{name} : {type} {initValue}"); + else + WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs index f826271..77eb9f7 100644 --- a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromDirGenerator.cs @@ -88,7 +88,8 @@ public bool GeneratePlantUml(Dictionary parameters) parameters.ContainsKey("-attributeRequired"), excludeUmlBeginEndTags, parameters.ContainsKey("-addPackageTags"), - parameters.ContainsKey("-removeSystemCollectionsAssociations")); + parameters.ContainsKey("-removeSystemCollectionsAssociations"), + parameters.ContainsKey("-noGetSetForProperties")); gen.Generate(root); relationships.AddAll(gen.relationships); } diff --git a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs index e53165e..d7a8651 100644 --- a/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator/Generator/PlantUmlFromFileGenerator.cs @@ -55,7 +55,8 @@ public bool GeneratePlantUml(Dictionary parameters) parameters.ContainsKey("-attributeRequired"), parameters.ContainsKey("-excludeUmlBeginEndTags"), false, - parameters.ContainsKey("-removeSystemCollectionsAssociations")); + parameters.ContainsKey("-removeSystemCollectionsAssociations"), + parameters.ContainsKey("-noGetSetForProperties")); gen.Generate(root); } catch (Exception e) diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index dfef649..85c4530 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -33,7 +33,8 @@ enum OptionType ["-attributeRequired"] = OptionType.Switch, ["-excludeUmlBeginEndTags"] = OptionType.Switch, ["-addPackageTags"] = OptionType.Switch, - ["-removeSystemCollectionsAssociations"] = OptionType.Switch + ["-removeSystemCollectionsAssociations"] = OptionType.Switch, + ["-noGetSetForProperties"] = OptionType.Switch }; static int Main(string[] args) From 3af8f3761eb6cb81c6d15980066eee2eb46e5a28 Mon Sep 17 00:00:00 2001 From: akravtsova Date: Fri, 13 Dec 2024 14:22:57 +0600 Subject: [PATCH 7/9] update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 39ec32e..9e24179 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is a generator to create a class-diagram of PlantUML from the C# source cod | Version | Commit | Comment | |---------|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| -| 1.4 | [d97e179](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/d97e17911111f27906d2a5a3eeb3f8137ebdb979) | Add "-noGetSetForProperties" option | +| 1.4 | [ebc52e8](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/ebc52e88f4719d28948f881de67796fc71c88cbd) | Add "-noGetSetForProperties" option | | 1.3 | [d97e179](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/d97e17911111f27906d2a5a3eeb3f8137ebdb979) | Add "-removeSystemCollectionsAssociations" option | | 1.2 | [cd9eed2](https://github.com/AnastasiaKallisto/PlantUmlClassDiagramGenerator/commit/cd9eed2539f9bac73410e6b6f1566ce979429f6f) | Add "-addPackageTags" option | | 1.1 | [e73b4fe](https://github.com/pierre3/PlantUmlClassDiagramGenerator/commit/e73b4feed9cd261271eb990a9c859f53536e8d7c) | Add "-excludeUmlBeginEndTags" option | @@ -51,6 +51,7 @@ puml-gen InputPath [OutputPath] [-dir] [-addPackageTags] [-public | -ignore Igno - **-dir**: (Optional) Specify when InputPath and OutputPath are directory names. - **-addPackageTags:** (Optional) If there is "-dir" tag, then program adds "package" tags and puts all relations in the end of include.puml. Relations will not be shown in other files - **-removeSystemCollectionsAssociations**: (Optional) If there are properties or fields like "IList" and other SystemCollections, there will be no relation with IList, but relation with T will be shown, if it isn't base type (string, int ...) +- **-noGetSetForProperties**: (Optional) Remover <\> and <\> for properties in classes - **-public:** (Optional) If specified, only public accessibility members are output. - **-ignore:** (Optional) Specify the accessibility of members to ignore, with a comma separated list. - **-excludePaths:** (Optional) Specify the exclude file and directory. From 1f3d98f569c4d3b363b7a4f867bce43f38f23c1c Mon Sep 17 00:00:00 2001 From: akravtsova Date: Fri, 13 Dec 2024 15:26:29 +0600 Subject: [PATCH 8/9] if there is dependency from the same class in fields, property, in difficult field and so on - distinct + equals and unique relations between classes in RelationshipCollection --- .../RelationshipGenerator.cs | 2 +- .../Relationship.cs | 27 +++++++++++++++++++ .../RelationshipCollection.cs | 8 ++++-- .../TypeNameText.cs | 21 +++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs index bac3b78..5beb5f3 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs @@ -19,6 +19,6 @@ public static string[] GenerateRelationships(RelationshipCollection relationship List strings = new List(); strings.AddRange(relationshipCollection.Select(r => r.ToString())); - return strings.ToArray(); + return strings.Distinct().ToArray(); } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs index 09363fc..4586065 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs @@ -13,4 +13,31 @@ public override string ToString() { return $"{baseTypeName.Identifier}{baseLabel} {symbol}{subLabel} {subTypeName.Identifier}{centerLabel}"; } + + private bool Equals(Relationship other) + { + return Equals(baseTypeName, other.baseTypeName) + && Equals(subTypeName, other.subTypeName) + && Equals(baseLabel, other.baseLabel) + && Equals(subLabel, other.subLabel); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Relationship)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (baseTypeName != null ? baseTypeName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (subTypeName != null ? subTypeName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (baseLabel != null ? baseLabel.GetHashCode() : 0); + return hashCode; + } + } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index 4e922fd..8cc1cdd 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -142,12 +142,16 @@ private static TypeNameText GetLeafName(string attributeName, TypeSyntax typeSyn private void AddeRationship(PlantUmlAssociationAttribute attribute, TypeNameText leafName, TypeNameText rootName) { var symbol = string.IsNullOrEmpty(attribute.Association) ? "--" : attribute.Association; - items.Add(new Relationship(rootName, leafName, symbol, attribute.RootLabel, attribute.LeafLabel, attribute.Label)); + var relationship = new Relationship(rootName, leafName, symbol, attribute.RootLabel, attribute.LeafLabel, attribute.Label); + if (!items.Contains(relationship)) + items.Add(relationship); } private void AddRelationship(TypeNameText leafName, TypeNameText rootName, string symbol, string nodeIdentifier) { - items.Add(new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments)); + var relationship = new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments); + if (!items.Contains(relationship)) + items.Add(relationship); } public IEnumerator GetEnumerator() diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index 6c66655..4c01784 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -72,4 +72,25 @@ public static TypeNameText From(BaseTypeDeclarationSyntax syntax) TypeArguments = typeArgs }; } + + private bool Equals(TypeNameText other) + { + return Identifier == other.Identifier && TypeArguments == other.TypeArguments; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TypeNameText)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Identifier != null ? Identifier.GetHashCode() : 0) * 397) ^ (TypeArguments != null ? TypeArguments.GetHashCode() : 0); + } + } } \ No newline at end of file From f6867cab26f4dd01527f3e010071c64ecce2be3e Mon Sep 17 00:00:00 2001 From: akravtsova Date: Fri, 13 Dec 2024 15:26:29 +0600 Subject: [PATCH 9/9] if there is dependency from the same class in fields, property, in difficult field and so on - distinct + equals and unique relations between classes in RelationshipCollection --- .../RelationshipGenerator.cs | 2 +- .../Enums/SystemCollectionsTypes.cs | 1 + .../Relationship.cs | 27 +++++++++++++++++++ .../RelationshipCollection.cs | 8 ++++-- .../TypeNameText.cs | 21 +++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs index bac3b78..5beb5f3 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator/RelationshipGenerator.cs @@ -19,6 +19,6 @@ public static string[] GenerateRelationships(RelationshipCollection relationship List strings = new List(); strings.AddRange(relationshipCollection.Select(r => r.ToString())); - return strings.ToArray(); + return strings.Distinct().ToArray(); } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs b/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs index 4640437..4b4fc14 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Enums/SystemCollectionsTypes.cs @@ -16,6 +16,7 @@ public enum SystemCollectionsTypes SortedList, Stack, StructuralComparisons, + List, // Actually not SystemCollections but... meh // structs DictionaryEntry, diff --git a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs index 09363fc..4586065 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs @@ -13,4 +13,31 @@ public override string ToString() { return $"{baseTypeName.Identifier}{baseLabel} {symbol}{subLabel} {subTypeName.Identifier}{centerLabel}"; } + + private bool Equals(Relationship other) + { + return Equals(baseTypeName, other.baseTypeName) + && Equals(subTypeName, other.subTypeName) + && Equals(baseLabel, other.baseLabel) + && Equals(subLabel, other.subLabel); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Relationship)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (baseTypeName != null ? baseTypeName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (subTypeName != null ? subTypeName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (baseLabel != null ? baseLabel.GetHashCode() : 0); + return hashCode; + } + } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index 4e922fd..8cc1cdd 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -142,12 +142,16 @@ private static TypeNameText GetLeafName(string attributeName, TypeSyntax typeSyn private void AddeRationship(PlantUmlAssociationAttribute attribute, TypeNameText leafName, TypeNameText rootName) { var symbol = string.IsNullOrEmpty(attribute.Association) ? "--" : attribute.Association; - items.Add(new Relationship(rootName, leafName, symbol, attribute.RootLabel, attribute.LeafLabel, attribute.Label)); + var relationship = new Relationship(rootName, leafName, symbol, attribute.RootLabel, attribute.LeafLabel, attribute.Label); + if (!items.Contains(relationship)) + items.Add(relationship); } private void AddRelationship(TypeNameText leafName, TypeNameText rootName, string symbol, string nodeIdentifier) { - items.Add(new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments)); + var relationship = new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments); + if (!items.Contains(relationship)) + items.Add(relationship); } public IEnumerator GetEnumerator() diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index 6c66655..4c01784 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -72,4 +72,25 @@ public static TypeNameText From(BaseTypeDeclarationSyntax syntax) TypeArguments = typeArgs }; } + + private bool Equals(TypeNameText other) + { + return Identifier == other.Identifier && TypeArguments == other.TypeArguments; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TypeNameText)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Identifier != null ? Identifier.GetHashCode() : 0) * 397) ^ (TypeArguments != null ? TypeArguments.GetHashCode() : 0); + } + } } \ No newline at end of file