From ad61b727472363bd0f8102355be12eafa0fe654f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Thu, 7 Dec 2023 17:21:32 +0100 Subject: [PATCH 01/28] feat: upgrade projects to .net8.0 --- .../PlantUmlClassDiagramGenerator.Library.csproj | 2 +- .../PlantUmlClassDiagramGenerator.csproj | 2 +- .../PlantUmlClassDiagramGenerator.Attributes.csproj | 2 +- .../PlantUmlClassDiagramGeneratorTest.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj b/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj index 33439ee..28a8f97 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj +++ b/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 diff --git a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj index 773cc41..bc1a996 100644 --- a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj +++ b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj @@ -1,7 +1,7 @@  Exe - net6.0 + net8.0 true puml-gen This is a generator to create a class-diagram of PlantUML from the C# source code. diff --git a/src/PlantUmlClassDiagramgenerator.Attributes/PlantUmlClassDiagramGenerator.Attributes.csproj b/src/PlantUmlClassDiagramgenerator.Attributes/PlantUmlClassDiagramGenerator.Attributes.csproj index bbb70e1..0db1105 100644 --- a/src/PlantUmlClassDiagramgenerator.Attributes/PlantUmlClassDiagramGenerator.Attributes.csproj +++ b/src/PlantUmlClassDiagramgenerator.Attributes/PlantUmlClassDiagramGenerator.Attributes.csproj @@ -1,6 +1,6 @@ - netstandard1.0 + netstandard2.1 This is an attribute classes liblary for the PlantUmlClassDiagramGenerator. https://www.nuget.org/packages/PlantUmlClassDiagramGenerator diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index ab615e9..81aa245 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false From ef1b78c3ff2f8d5c4640d96facb6a29ea11623c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Thu, 7 Dec 2023 17:26:15 +0100 Subject: [PATCH 02/28] feat: upgrade nuget packages --- .../PlantUmlClassDiagramGenerator.Library.csproj | 2 +- .../PlantUmlClassDiagramGeneratorTest.csproj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj b/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj index 28a8f97..6c98c1e 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj +++ b/src/PlantUmlClassDiagramGenerator.Library/PlantUmlClassDiagramGenerator.Library.csproj @@ -5,7 +5,7 @@ - + diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index 81aa245..540ef32 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -6,9 +6,9 @@ - - - + + + From 5873658789bcbfaef18bb73e5f2b9a7571318c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Thu, 7 Dec 2023 17:38:59 +0100 Subject: [PATCH 03/28] refactor: roslyn code smells fixes --- .../ClassDiagramGenerator.cs | 835 +++++++++--------- .../InheritanceRelationship.cs | 5 +- .../Relationship.cs | 24 +- .../TypeNameText.cs | 4 +- .../ExcludeFileFilter.cs | 2 +- src/PlantUmlClassDiagramGenerator/Program.cs | 37 +- .../ClassDiagramGeneratorTest.cs | 28 +- .../UnitTests/ExcludeFileFilterTest.cs | 2 +- 8 files changed, 443 insertions(+), 494 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index 5402087..d1c2300 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -8,264 +8,212 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +public class ClassDiagramGenerator( + TextWriter writer, + string indent, + Accessibilities ignoreMemberAccessibilities = Accessibilities.None, + bool createAssociation = true, + bool attributeRequired = false, + bool excludeUmlBeginEndTags = false) : CSharpSyntaxWalker { - public class ClassDiagramGenerator : CSharpSyntaxWalker + private readonly HashSet types = []; + private readonly List additionalTypeDeclarationNodes = []; + private readonly Accessibilities ignoreMemberAccessibilities = ignoreMemberAccessibilities; + private 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 Dictionary escapeDictionary = new() { - private readonly HashSet types = new HashSet(); - private readonly IList additionalTypeDeclarationNodes; - private readonly Accessibilities ignoreMemberAccessibilities; - private readonly RelationshipCollection relationships = new RelationshipCollection(); - private readonly TextWriter writer; - private readonly string indent; - private int nestingDepth = 0; - private readonly bool createAssociation; - private readonly bool attributeRequired; - private readonly bool excludeUmlBeginEndTags; - private readonly Dictionary escapeDictionary = new Dictionary - { - {@"(?[^{]){(?{[^{])", "${before}{${after}"}, - {@"(?[^}])}(?[^}])", "${before}}${after}"}, - }; + {@"(?[^{]){(?{[^{])", "${before}{${after}"}, + {@"(?[^}])}(?[^}])", "${before}}${after}"}, + }; - public ClassDiagramGenerator( - TextWriter writer, - string indent, - Accessibilities ignoreMemberAccessibilities = Accessibilities.None, - bool createAssociation = true, - bool attributeRequired = false, - bool excludeUmlBeginEndTags = false) - { - this.writer = writer; - this.indent = indent; - additionalTypeDeclarationNodes = new List(); - this.ignoreMemberAccessibilities = ignoreMemberAccessibilities; - this.createAssociation = createAssociation; - this.attributeRequired = attributeRequired; - this.excludeUmlBeginEndTags = excludeUmlBeginEndTags; - } + public void Generate(SyntaxNode root) + { + if (!this.excludeUmlBeginEndTags) WriteLine("@startuml"); + GenerateInternal(root); + if (!this.excludeUmlBeginEndTags) WriteLine("@enduml"); + } - public void Generate(SyntaxNode root) - { - if (!this.excludeUmlBeginEndTags) WriteLine("@startuml"); - GenerateInternal(root); - if (!this.excludeUmlBeginEndTags) WriteLine("@enduml"); - } + public void GenerateInternal(SyntaxNode root) + { + Visit(root); + GenerateAdditionalTypeDeclarations(); + GenerateRelationships(); + } - public void GenerateInternal(SyntaxNode root) - { - Visit(root); - GenerateAdditionalTypeDeclarations(); - GenerateRelationships(); - } + 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 VisitInterfaceDeclaration(InterfaceDeclarationSyntax 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.TrimEntries); + 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) { - VisitTypeDeclaration(node, () => base.VisitInterfaceDeclaration(node)); + VisitRecordParameter(node, type, typeParams, parameter); } + base.VisitRecordDeclaration(node); + nestingDepth--; + + WriteLine("}"); + } - public override void VisitClassDeclaration(ClassDeclarationSyntax node) + 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) { - VisitTypeDeclaration(node, () => base.VisitClassDeclaration(node)); + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, parameter, associationAttr); } - - public override void VisitRecordDeclaration(RecordDeclarationSyntax node) + else if (!createAssociation + || parameter.AttributeLists.HasIgnoreAssociationAttribute() + || parameterType.GetType() == typeof(PredefinedTypeSyntax) + || parameterType.GetType() == typeof(NullableTypeSyntax) + || isTypeParameterProp) { - 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.TrimEntries); - 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("}"); + // 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}"); } - - private void VisitRecordParameter(RecordDeclarationSyntax node, string type, string[] typeParams, ParameterSyntax parameter) + else { - 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)) { - if (type.GetType() == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(parameterType); - } - relationships.AddAssociationFrom(parameter, node); + 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}"; + public override void VisitStructDeclaration(StructDeclarationSyntax node) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } - types.Add(name); + relationships.AddInnerclassRelationFrom(node); + relationships.AddInheritanceFrom(node); - WriteLine($"struct {type} {{"); + var typeName = TypeNameText.From(node); + var name = typeName.Identifier; + var typeParam = typeName.TypeArguments; + var type = $"{name}{typeParam}"; - nestingDepth++; - base.VisitStructDeclaration(node); - nestingDepth--; + types.Add(name); - WriteLine("}"); - } + WriteLine($"struct {type} {{"); - public override void VisitEnumDeclaration(EnumDeclarationSyntax node) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } + nestingDepth++; + base.VisitStructDeclaration(node); + nestingDepth--; - relationships.AddInnerclassRelationFrom(node); + WriteLine("}"); + } - var type = $"{node.Identifier}"; + public override void VisitEnumDeclaration(EnumDeclarationSyntax node) + { + if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (SkipInnerTypeDeclaration(node)) { return; } - types.Add(type); + relationships.AddInnerclassRelationFrom(node); - WriteLine($"{node.EnumKeyword} {type} {{"); + var type = $"{node.Identifier}"; - nestingDepth++; - base.VisitEnumDeclaration(node); - nestingDepth--; + types.Add(type); - WriteLine("}"); - } + WriteLine($"{node.EnumKeyword} {type} {{"); - 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}"); + nestingDepth++; + base.VisitEnumDeclaration(node); + nestingDepth--; - WriteLine($"{modifiers}{name}({string.Join(", ", args)})"); - } + WriteLine("}"); + } - public override void VisitFieldDeclaration(FieldDeclarationSyntax node) + public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + foreach (var parameter in node.ParameterList?.Parameters) { - 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) + var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) { - 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); - } + 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}"); - public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) - { - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (IsIgnoreMember(node.Modifiers)) { return; } + WriteLine($"{modifiers}{name}({string.Join(", ", args)})"); + } - var type = node.Type; + public override void VisitFieldDeclaration(FieldDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } - var parentClass = (node.Parent as TypeDeclarationSyntax); - var isTypeParameterProp = parentClass?.TypeParameterList?.Parameters - .Any(t => t.Identifier.Text == type.ToString()) ?? false; + 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) { @@ -274,284 +222,323 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) } else if (!createAssociation || node.AttributeLists.HasIgnoreAssociationAttribute() - || type.GetType() == typeof(PredefinedTypeSyntax) - || type.GetType() == typeof(NullableTypeSyntax) - || isTypeParameterProp) + || fieldType == typeof(PredefinedTypeSyntax) + || fieldType == typeof(NullableTypeSyntax) + || isTypeParameterField) { - 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 useLiteralInit = field.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))) + ? (" = " + escapeDictionary.Aggregate(field.Initializer.Value.ToString(), + (f, e) => Regex.Replace(f, e.Key, e.Value))) : ""; - - WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); + WriteLine($"{modifiers}{field.Identifier} : {type}{initValue}"); } else { - if (type.GetType() == typeof(GenericNameSyntax)) + if (fieldType == typeof(GenericNameSyntax)) { additionalTypeDeclarationNodes.Add(type); } - relationships.AddAssociationFrom(node); + 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; - private static PlantUmlAssociationAttribute CreateAssociationAttribute(AttributeSyntax associationAttribute) + var associationAttrSyntax = node.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) { - var attributeProps = associationAttribute.ArgumentList.Arguments.Select(arg => new - { - Name = arg.NameEquals.Name.ToString(), - Value = arg.Expression.GetLastToken().ValueText - }); - return new PlantUmlAssociationAttribute() - { - Association = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Association))?.Value, - Name = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Name))?.Value, - RootLabel = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.RootLabel))?.Value, - LeafLabel = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.LeafLabel))?.Value, - Label = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Label))?.Value - }; + var associationAttr = CreateAssociationAttribute(associationAttrSyntax); + relationships.AddAssociationFrom(node, associationAttr); } - - public override void VisitMethodDeclaration(MethodDeclarationSyntax node) + else if (!createAssociation + || node.AttributeLists.HasIgnoreAssociationAttribute() + || type.GetType() == typeof(PredefinedTypeSyntax) + || type.GetType() == typeof(NullableTypeSyntax) + || isTypeParameterProp) { - 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)); + 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}"); + //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}({string.Join(", ", args)}) : {returnType}"); + WriteLine($"{modifiers}{name} : {type} {accessorStr}{initValue}"); } - - public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) + else { - WriteLine($"{node.Identifier}{node.EqualsValue},"); + if (type.GetType() == typeof(GenericNameSyntax)) + { + additionalTypeDeclarationNodes.Add(type); + } + relationships.AddAssociationFrom(node); } + } - public override void VisitEventFieldDeclaration(EventFieldDeclarationSyntax node) + private static PlantUmlAssociationAttribute CreateAssociationAttribute(AttributeSyntax associationAttribute) + { + var attributeProps = associationAttribute.ArgumentList.Arguments.Select(arg => new { - 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} "); - } + Name = arg.NameEquals.Name.ToString(), + Value = arg.Expression.GetLastToken().ValueText + }); + return new PlantUmlAssociationAttribute() + { + Association = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Association))?.Value, + Name = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Name))?.Value, + RootLabel = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.RootLabel))?.Value, + LeafLabel = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.LeafLabel))?.Value, + Label = attributeProps.FirstOrDefault(prop => prop.Name == nameof(PlantUmlAssociationAttribute.Label))?.Value + }; + } - public override void VisitGenericName(GenericNameSyntax node) + public override void VisitMethodDeclaration(MethodDeclarationSyntax node) + { + if (node.AttributeLists.HasIgnoreAttribute()) { return; } + if (IsIgnoreMember(node.Modifiers)) { return; } + foreach (var parameter in node.ParameterList?.Parameters) { - if (createAssociation) + var associationAttrSyntax = parameter.AttributeLists.GetAssociationAttributeSyntax(); + if (associationAttrSyntax is not null) { - additionalTypeDeclarationNodes.Add(node); + 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}"); - private void WriteLine(string line) - { - var space = string.Concat(Enumerable.Repeat(indent, nestingDepth)); - writer.WriteLine(space + line); - } + WriteLine($"{modifiers}{name}({string.Join(", ", args)}) : {returnType}"); + } - private bool SkipInnerTypeDeclaration(SyntaxNode node) - { - if (nestingDepth <= 0) return false; + 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) + { + if (createAssociation) + { additionalTypeDeclarationNodes.Add(node); - return true; } + } + + private void WriteLine(string line) + { + var space = string.Concat(Enumerable.Repeat(indent, nestingDepth)); + writer.WriteLine(space + line); + } + + private bool SkipInnerTypeDeclaration(SyntaxNode node) + { + if (nestingDepth <= 0) return false; + + additionalTypeDeclarationNodes.Add(node); + return true; + } - private void GenerateAdditionalTypeDeclarations() + private void GenerateAdditionalTypeDeclarations() + { + for (int i = 0; i < additionalTypeDeclarationNodes.Count; i++) { - for (int i = 0; i < additionalTypeDeclarationNodes.Count; i++) + SyntaxNode node = additionalTypeDeclarationNodes[i]; + if (node is GenericNameSyntax genericNode) { - SyntaxNode node = additionalTypeDeclarationNodes[i]; - if (node is GenericNameSyntax genericNode) + if (createAssociation) { - if (createAssociation) - { - GenerateAdditionalGenericTypeDeclaration(genericNode); - } - continue; + GenerateAdditionalGenericTypeDeclaration(genericNode); } - Visit(node); + continue; } + Visit(node); } + } - private void GenerateAdditionalGenericTypeDeclaration(GenericNameSyntax genericNode) + private void GenerateAdditionalGenericTypeDeclaration(GenericNameSyntax genericNode) + { + var typename = TypeNameText.From(genericNode); + if (!types.Contains(typename.Identifier)) { - var typename = TypeNameText.From(genericNode); - if (!types.Contains(typename.Identifier)) - { - WriteLine($"class {typename.Identifier}{typename.TypeArguments} {{"); - WriteLine("}"); - types.Add(typename.Identifier); - } + WriteLine($"class {typename.Identifier}{typename.TypeArguments} {{"); + WriteLine("}"); + types.Add(typename.Identifier); } + } - private void GenerateRelationships() + private void GenerateRelationships() + { + foreach (var relationship in relationships) { - foreach (var relationship in relationships) - { - WriteLine(relationship.ToString()); - } + WriteLine(relationship.ToString()); } + } - private void VisitTypeDeclaration(TypeDeclarationSyntax node, Action visitBase) - { - if (attributeRequired && !node.AttributeLists.HasDiagramAttribute()) { return; } - if (node.AttributeLists.HasIgnoreAttribute()) { return; } - if (SkipInnerTypeDeclaration(node)) { return; } + 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); + relationships.AddInnerclassRelationFrom(node); + relationships.AddInheritanceFrom(node); - var modifiers = GetTypeModifiersText(node.Modifiers); - var keyword = (node.Modifiers.Any(SyntaxKind.AbstractKeyword) ? "abstract " : "") - + node.Keyword.ToString(); + 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}"; + var typeName = TypeNameText.From(node); + var name = typeName.Identifier; + var typeParam = typeName.TypeArguments; + var type = $"{name}{typeParam}"; - types.Add(name); + types.Add(name); - WriteLine($"{keyword} {type} {modifiers}{{"); + WriteLine($"{keyword} {type} {modifiers}{{"); - nestingDepth++; - visitBase(); - nestingDepth--; + nestingDepth++; + visitBase(); + nestingDepth--; - WriteLine("}"); - } + WriteLine("}"); + } - private string GetTypeModifiersText(SyntaxTokenList modifiers) + private static string GetTypeModifiersText(SyntaxTokenList modifiers) + { + var tokens = modifiers.Select(token => { - var tokens = modifiers.Select(token => + switch (token.Kind()) { - switch (token.Kind()) - { - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.InternalKeyword: - case SyntaxKind.AbstractKeyword: - return ""; - default: - return $"<<{token.ValueText}>>"; - } - }).Where(token => token != ""); - - var result = string.Join(" ", tokens); - if (result != string.Empty) - { - result += " "; - }; - return result; - } + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.InternalKeyword: + case SyntaxKind.AbstractKeyword: + return ""; + default: + return $"<<{token.ValueText}>>"; + } + }).Where(token => token != ""); - private bool IsIgnoreMember(SyntaxTokenList modifiers) + var result = string.Join(" ", tokens); + if (result != string.Empty) { - if (ignoreMemberAccessibilities == Accessibilities.None) { return false; } + result += " "; + }; + return result; + } - var tokenKinds = HasAccessModifier(modifiers) - ? modifiers.Select(x => x.Kind()).ToArray() - : new[] { SyntaxKind.PrivateKeyword }; + private bool IsIgnoreMember(SyntaxTokenList modifiers) + { + if (ignoreMemberAccessibilities == Accessibilities.None) { return false; } - if (ignoreMemberAccessibilities.HasFlag(Accessibilities.ProtectedInternal) - && tokenKinds.Contains(SyntaxKind.ProtectedKeyword) - && tokenKinds.Contains(SyntaxKind.InternalKeyword)) - { - return true; - } + var tokenKinds = HasAccessModifier(modifiers) + ? modifiers.Select(x => x.Kind()).ToArray() + : [SyntaxKind.PrivateKeyword]; - if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Public) - && tokenKinds.Contains(SyntaxKind.PublicKeyword)) - { - return true; - } + if (ignoreMemberAccessibilities.HasFlag(Accessibilities.ProtectedInternal) + && tokenKinds.Contains(SyntaxKind.ProtectedKeyword) + && tokenKinds.Contains(SyntaxKind.InternalKeyword)) + { + return true; + } - if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Protected) - && tokenKinds.Contains(SyntaxKind.ProtectedKeyword)) - { - return true; - } + if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Public) + && tokenKinds.Contains(SyntaxKind.PublicKeyword)) + { + return true; + } - if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Internal) - && tokenKinds.Contains(SyntaxKind.InternalKeyword)) - { - return true; - } + if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Protected) + && tokenKinds.Contains(SyntaxKind.ProtectedKeyword)) + { + return true; + } - if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Private) - && tokenKinds.Contains(SyntaxKind.PrivateKeyword)) - { - return true; - } - return false; + if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Internal) + && tokenKinds.Contains(SyntaxKind.InternalKeyword)) + { + return true; } - private string GetMemberModifiersText( - SyntaxTokenList modifiers, - bool isInterfaceMember) + if (ignoreMemberAccessibilities.HasFlag(Accessibilities.Private) + && tokenKinds.Contains(SyntaxKind.PrivateKeyword)) { - var tokens = modifiers.Select(token => - { - return token.Kind() switch - { - SyntaxKind.PublicKeyword => "+", - SyntaxKind.PrivateKeyword => "-", - SyntaxKind.ProtectedKeyword => "#", - SyntaxKind.AbstractKeyword or SyntaxKind.StaticKeyword => $"{{{token.ValueText}}}", - _ => $"<<{token.ValueText}>>", - }; - }).ToList(); - if (!isInterfaceMember && !HasAccessModifier(modifiers)) - { - tokens.Add("-"); - } - var result = string.Join(" ", tokens); - if (result != string.Empty) - { - result += " "; - }; - return result; + return true; } + return false; + } - private static bool HasAccessModifier(SyntaxTokenList modifiers) + private static string GetMemberModifiersText( + SyntaxTokenList modifiers, + bool isInterfaceMember) + { + var tokens = modifiers.Select(token => + { + return token.Kind() switch + { + SyntaxKind.PublicKeyword => "+", + SyntaxKind.PrivateKeyword => "-", + SyntaxKind.ProtectedKeyword => "#", + SyntaxKind.AbstractKeyword or SyntaxKind.StaticKeyword => $"{{{token.ValueText}}}", + _ => $"<<{token.ValueText}>>", + }; + }).ToList(); + if (!isInterfaceMember && !HasAccessModifier(modifiers)) { - return modifiers.Any(token => - token.IsKind(SyntaxKind.PublicKeyword) - || token.IsKind(SyntaxKind.PrivateKeyword) - || token.IsKind(SyntaxKind.ProtectedKeyword) - || token.IsKind(SyntaxKind.InternalKeyword)); + tokens.Add("-"); } + var result = string.Join(" ", tokens); + if (result != string.Empty) + { + result += " "; + }; + return result; } + private static bool HasAccessModifier(SyntaxTokenList modifiers) + { + return modifiers.Any(token => + token.IsKind(SyntaxKind.PublicKeyword) + || token.IsKind(SyntaxKind.PrivateKeyword) + || token.IsKind(SyntaxKind.ProtectedKeyword) + || token.IsKind(SyntaxKind.InternalKeyword)); + } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs b/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs index e79affc..a63e1bf 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs @@ -1,9 +1,6 @@ namespace PlantUmlClassDiagramGenerator.Library { - public class InheritanceRelationship : Relationship + public class InheritanceRelationship(TypeNameText baseTypeName, TypeNameText subTypeName) : Relationship(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments) { - public InheritanceRelationship(TypeNameText baseTypeName, TypeNameText subTypeName): base(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments) - { - } } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs index c2792bf..f2ce429 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs @@ -1,23 +1,13 @@ namespace PlantUmlClassDiagramGenerator.Library { - public class Relationship + public class Relationship(TypeNameText baseTypeName, TypeNameText subTypeName, string symbol, string baseLabel = "", string subLabel = "", string centerLabel = "") { - protected TypeNameText baseTypeName; - protected TypeNameText subTypeName; - protected string baseLabel; - protected string subLabel; - protected string centerLabel; - private readonly string symbol; - - public Relationship(TypeNameText baseTypeName, TypeNameText subTypeName, string symbol, string baseLabel = "", string subLabel = "", string centerLabel="") - { - this.baseTypeName = baseTypeName; - this.subTypeName = subTypeName; - this.symbol = symbol; - this.baseLabel = string.IsNullOrWhiteSpace(baseLabel) ? "" : $" \"{baseLabel}\""; - this.subLabel = string.IsNullOrWhiteSpace(subLabel) ? "" : $" \"{subLabel}\""; - this.centerLabel = string.IsNullOrWhiteSpace(centerLabel) ? "" : $" : \"{centerLabel}\""; - } + protected TypeNameText baseTypeName = baseTypeName; + protected TypeNameText subTypeName = subTypeName; + protected string baseLabel = string.IsNullOrWhiteSpace(baseLabel) ? "" : $" \"{baseLabel}\""; + protected string subLabel = string.IsNullOrWhiteSpace(subLabel) ? "" : $" \"{subLabel}\""; + protected string centerLabel = string.IsNullOrWhiteSpace(centerLabel) ? "" : $" : \"{centerLabel}\""; + private readonly string symbol = symbol; public override string ToString() { diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index 8e02e1e..d88d605 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -18,7 +18,7 @@ public static TypeNameText From(SimpleNameSyntax syntax) identifier = $"\"{identifier}`{count}\""; typeArgs = "<" + string.Join(",", genericName.TypeArgumentList.Arguments) + ">"; } - else if (identifier.StartsWith("@")) + else if (identifier.StartsWith('@')) { identifier = $"\"{identifier}\""; } @@ -62,7 +62,7 @@ public static TypeNameText From(BaseTypeDeclarationSyntax syntax) identifier = $"\"{identifier}`{count}\""; typeArgs = "<" + string.Join(",", typeDeclaration.TypeParameterList.Parameters) + ">"; } - else if (identifier.StartsWith("@")) + else if (identifier.StartsWith('@')) { identifier = $"\"{identifier}\""; } diff --git a/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs b/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs index 6023c11..9e4544d 100644 --- a/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs +++ b/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs @@ -7,7 +7,7 @@ namespace PlantUmlClassDiagramGenerator { public class ExcludeFileFilter { - public IEnumerable GetFilesToProcess(IEnumerable files, IList excludePaths, string inputRoot) + public static IEnumerable GetFilesToProcess(IEnumerable files, IList excludePaths, string inputRoot) { return files.Where(f => !IsFileExcluded(f, excludePaths, inputRoot)); } diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index f11de97..779ece1 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -18,7 +18,7 @@ enum OptionType Switch } - static readonly Dictionary options = new Dictionary() + static readonly Dictionary options = new() { ["-dir"] = OptionType.Switch, ["-public"] = OptionType.Switch, @@ -30,8 +30,6 @@ enum OptionType ["-excludeUmlBeginEndTags"] = OptionType.Switch }; - static readonly ExcludeFileFilter _excludeFileFilter = new ExcludeFileFilter(); - static int Main(string[] args) { Dictionary parameters = MakeParameters(args); @@ -60,9 +58,9 @@ private static bool GeneratePlantUmlFromFile(Dictionary paramete return false; } string outputFileName; - if (parameters.ContainsKey("out")) + if (parameters.TryGetValue("out", out string value)) { - outputFileName = parameters["out"]; + outputFileName = value; try { var outdir = Path.GetDirectoryName(outputFileName); @@ -117,9 +115,9 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter // Use GetFullPath to fully support relative paths. var outputRoot = Path.GetFullPath(inputRoot); - if (parameters.ContainsKey("out")) + if (parameters.TryGetValue("out", out string outValue)) { - outputRoot = parameters["out"]; + outputRoot = outValue; try { Directory.CreateDirectory(outputRoot); @@ -141,10 +139,10 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter .Select(path => path.Trim()) .ToList(); } - if (parameters.ContainsKey("-excludePaths")) + if (parameters.TryGetValue("-excludePaths", out string excludePathValue)) { var splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; - excludePaths.AddRange(parameters["-excludePaths"].Split(',', splitOptions)); + excludePaths.AddRange(excludePathValue.Split(',', splitOptions)); } var excludeUmlBeginEndTags = parameters.ContainsKey("-excludeUmlBeginEndTags"); @@ -154,7 +152,7 @@ private static bool GeneratePlantUmlFromDir(Dictionary parameter if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@startuml"); var error = false; - var filesToProcess = _excludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); + var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); foreach (var inputFile in filesToProcess) { Console.WriteLine($"Processing \"{inputFile}\"..."); @@ -227,9 +225,9 @@ private static Accessibilities GetIgnoreAccessibilities(Dictionary s.Replace("\\", "/")).ToArray(); } // Act - List result = testObject.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); + List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); // Assert string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); From 3a747c71ebd79391252bc80b14ff17ca98b1c7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Thu, 7 Dec 2023 17:48:52 +0100 Subject: [PATCH 04/28] refactor: convert namespaces to file scoped namespaces --- .../Accessibilities.cs | 21 +- .../AttributeListSyntaxListExtensions.cs | 91 ++-- .../InheritanceRelationship.cs | 7 +- .../Relationship.cs | 25 +- .../RelationshipCollection.cs | 243 +++++----- .../TypeNameText.cs | 119 +++-- .../ExcludeFileFilter.cs | 48 +- .../PathHelper.cs | 15 +- src/PlantUmlClassDiagramGenerator/Program.cs | 407 ++++++++-------- .../ClassDiagramGeneratorTest.cs | 443 +++++++++--------- .../UnitTests/ExcludeFileFilterTest.cs | 85 ++-- .../testData/AtPrefixType.cs | 31 +- .../testData/AttributeRequired.cs | 155 +++--- .../testData/Attributes.cs | 99 ++-- .../testData/CurlyBrackets.cs | 17 +- .../testData/DefaultModifierType.cs | 51 +- .../testData/GenericsType.cs | 87 ++-- .../testData/InputClasses.cs | 185 ++++---- .../testData/NullableType.cs | 9 +- .../testData/RecordType.cs | 55 ++- 20 files changed, 1081 insertions(+), 1112 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/Accessibilities.cs b/src/PlantUmlClassDiagramGenerator.Library/Accessibilities.cs index 0c8635e..48267e3 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Accessibilities.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Accessibilities.cs @@ -1,15 +1,14 @@ using System; -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +[Flags] +public enum Accessibilities { - [Flags] - public enum Accessibilities - { - None = 0x0000, - Private = 0x0001, - Protected = 0x0002, - Internal = 0x0004, - ProtectedInternal = 0x0008, - Public = 0x0010, - } + None = 0x0000, + Private = 0x0001, + Protected = 0x0002, + Internal = 0x0004, + ProtectedInternal = 0x0008, + Public = 0x0010, } diff --git a/src/PlantUmlClassDiagramGenerator.Library/AttributeListSyntaxListExtensions.cs b/src/PlantUmlClassDiagramGenerator.Library/AttributeListSyntaxListExtensions.cs index 85d9616..3ef5dcb 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/AttributeListSyntaxListExtensions.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/AttributeListSyntaxListExtensions.cs @@ -3,62 +3,61 @@ using PlantUmlClassDiagramGenerator.Attributes; using System.Linq; -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +static class AttributeListSyntaxListExtensions { - static class AttributeListSyntaxListExtensions + public static bool HasIgnoreAttribute(this SyntaxList attributeLists) { - public static bool HasIgnoreAttribute(this SyntaxList attributeLists) - { - return GetIgnoreAttribute(attributeLists) is not null; - } + return GetIgnoreAttribute(attributeLists) is not null; + } - public static AttributeSyntax GetIgnoreAttribute(this SyntaxList attributeLists) - { - return attributeLists.SelectMany(list => list.Attributes) - .FirstOrDefault( - attr => attr.Name.ToString() == nameof(PlantUmlIgnoreAttribute) - || attr.Name.ToString() == nameof(PlantUmlIgnoreAttribute).Replace("Attribute", "")); - } + public static AttributeSyntax GetIgnoreAttribute(this SyntaxList attributeLists) + { + return attributeLists.SelectMany(list => list.Attributes) + .FirstOrDefault( + attr => attr.Name.ToString() == nameof(PlantUmlIgnoreAttribute) + || attr.Name.ToString() == nameof(PlantUmlIgnoreAttribute).Replace("Attribute", "")); + } - public static bool HasIgnoreAssociationAttribute(this SyntaxList attributeLists) - { - return GetIgnoreAssociationAttribute(attributeLists) is not null; - } + public static bool HasIgnoreAssociationAttribute(this SyntaxList attributeLists) + { + return GetIgnoreAssociationAttribute(attributeLists) is not null; + } - public static AttributeSyntax GetIgnoreAssociationAttribute(this SyntaxList attributeLists) - { - return attributeLists.SelectMany(list => list.Attributes) - .FirstOrDefault( - attr => attr.Name.ToString() == nameof(PlantUmlIgnoreAssociationAttribute) - || attr.Name.ToString() == nameof(PlantUmlIgnoreAssociationAttribute).Replace("Attribute", "")); - } + public static AttributeSyntax GetIgnoreAssociationAttribute(this SyntaxList attributeLists) + { + return attributeLists.SelectMany(list => list.Attributes) + .FirstOrDefault( + attr => attr.Name.ToString() == nameof(PlantUmlIgnoreAssociationAttribute) + || attr.Name.ToString() == nameof(PlantUmlIgnoreAssociationAttribute).Replace("Attribute", "")); + } - public static bool HasAssociationAttribute(this SyntaxList attributeLists) - { - return GetAssociationAttributeSyntax(attributeLists) is not null; + public static bool HasAssociationAttribute(this SyntaxList attributeLists) + { + return GetAssociationAttributeSyntax(attributeLists) is not null; - } + } - public static AttributeSyntax GetAssociationAttributeSyntax(this SyntaxList attributeLists) - { - return attributeLists.SelectMany(list => list.Attributes) - .FirstOrDefault( - attr => attr.Name.ToString() == nameof(PlantUmlAssociationAttribute) - || attr.Name.ToString() == nameof(PlantUmlAssociationAttribute).Replace("Attribute", "")); - } + public static AttributeSyntax GetAssociationAttributeSyntax(this SyntaxList attributeLists) + { + return attributeLists.SelectMany(list => list.Attributes) + .FirstOrDefault( + attr => attr.Name.ToString() == nameof(PlantUmlAssociationAttribute) + || attr.Name.ToString() == nameof(PlantUmlAssociationAttribute).Replace("Attribute", "")); + } - public static bool HasDiagramAttribute(this SyntaxList attributeLists) - { - return GetDiagramAttributeSyntax(attributeLists) is not null; + public static bool HasDiagramAttribute(this SyntaxList attributeLists) + { + return GetDiagramAttributeSyntax(attributeLists) is not null; - } + } - public static AttributeSyntax GetDiagramAttributeSyntax(this SyntaxList attributeLists) - { - return attributeLists.SelectMany(list => list.Attributes) - .FirstOrDefault( - attr => attr.Name.ToString() == nameof(PlantUmlDiagramAttribute) - || attr.Name.ToString() == nameof(PlantUmlDiagramAttribute).Replace("Attribute", "")); - } + public static AttributeSyntax GetDiagramAttributeSyntax(this SyntaxList attributeLists) + { + return attributeLists.SelectMany(list => list.Attributes) + .FirstOrDefault( + attr => attr.Name.ToString() == nameof(PlantUmlDiagramAttribute) + || attr.Name.ToString() == nameof(PlantUmlDiagramAttribute).Replace("Attribute", "")); } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs b/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs index a63e1bf..1928305 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/InheritanceRelationship.cs @@ -1,6 +1,5 @@ -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +public class InheritanceRelationship(TypeNameText baseTypeName, TypeNameText subTypeName) : Relationship(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments) { - public class InheritanceRelationship(TypeNameText baseTypeName, TypeNameText subTypeName) : Relationship(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments) - { - } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs index f2ce429..09363fc 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/Relationship.cs @@ -1,17 +1,16 @@ -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +public class Relationship(TypeNameText baseTypeName, TypeNameText subTypeName, string symbol, string baseLabel = "", string subLabel = "", string centerLabel = "") { - public class Relationship(TypeNameText baseTypeName, TypeNameText subTypeName, string symbol, string baseLabel = "", string subLabel = "", string centerLabel = "") - { - protected TypeNameText baseTypeName = baseTypeName; - protected TypeNameText subTypeName = subTypeName; - protected string baseLabel = string.IsNullOrWhiteSpace(baseLabel) ? "" : $" \"{baseLabel}\""; - protected string subLabel = string.IsNullOrWhiteSpace(subLabel) ? "" : $" \"{subLabel}\""; - protected string centerLabel = string.IsNullOrWhiteSpace(centerLabel) ? "" : $" : \"{centerLabel}\""; - private readonly string symbol = symbol; + protected TypeNameText baseTypeName = baseTypeName; + protected TypeNameText subTypeName = subTypeName; + protected string baseLabel = string.IsNullOrWhiteSpace(baseLabel) ? "" : $" \"{baseLabel}\""; + protected string subLabel = string.IsNullOrWhiteSpace(subLabel) ? "" : $" \"{subLabel}\""; + protected string centerLabel = string.IsNullOrWhiteSpace(centerLabel) ? "" : $" : \"{centerLabel}\""; + private readonly string symbol = symbol; - public override string ToString() - { - return $"{baseTypeName.Identifier}{baseLabel} {symbol}{subLabel} {subTypeName.Identifier}{centerLabel}"; - } + public override string ToString() + { + return $"{baseTypeName.Identifier}{baseLabel} {symbol}{subLabel} {subTypeName.Identifier}{centerLabel}"; } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index 908ba32..fd49b1f 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -4,151 +4,150 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +public class RelationshipCollection : IEnumerable { - public class RelationshipCollection : IEnumerable - { - private readonly IList items = new List(); + private readonly IList items = new List(); - public void AddInheritanceFrom(TypeDeclarationSyntax syntax) - { - if (syntax.BaseList == null) return; + public void AddInheritanceFrom(TypeDeclarationSyntax syntax) + { + if (syntax.BaseList == null) return; - var subTypeName = TypeNameText.From(syntax); + var subTypeName = TypeNameText.From(syntax); - foreach (var typeStntax in syntax.BaseList.Types) - { - if (typeStntax.Type is not SimpleNameSyntax typeNameSyntax) continue; - var baseTypeName = TypeNameText.From(typeNameSyntax); - items.Add(new Relationship(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments)); - } + foreach (var typeStntax in syntax.BaseList.Types) + { + if (typeStntax.Type is not SimpleNameSyntax typeNameSyntax) continue; + var baseTypeName = TypeNameText.From(typeNameSyntax); + items.Add(new Relationship(baseTypeName, subTypeName, "<|--", baseTypeName.TypeArguments)); } + } - public void AddInnerclassRelationFrom(SyntaxNode node) - { - if (node.Parent is not BaseTypeDeclarationSyntax outerTypeNode - || node is not BaseTypeDeclarationSyntax innerTypeNode) return; + public void AddInnerclassRelationFrom(SyntaxNode node) + { + if (node.Parent is not BaseTypeDeclarationSyntax outerTypeNode + || node is not BaseTypeDeclarationSyntax innerTypeNode) return; - var outerTypeName = TypeNameText.From(outerTypeNode); - var innerTypeName = TypeNameText.From(innerTypeNode); - items.Add(new Relationship(outerTypeName, innerTypeName, "+--")); - } + var outerTypeName = TypeNameText.From(outerTypeNode); + var innerTypeName = TypeNameText.From(innerTypeNode); + items.Add(new Relationship(outerTypeName, innerTypeName, "+--")); + } - public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSyntax field) - { - if (node.Declaration.Type is not SimpleNameSyntax leafNode - || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; - - var symbol = field.Initializer == null ? "-->" : "o->"; - var fieldIdentifier = field.Identifier.ToString(); - var leafName = TypeNameText.From(leafNode); - var rootName = TypeNameText.From(rootNode); - AddRelationship(leafName, rootName, symbol, fieldIdentifier); - } + public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSyntax field) + { + if (node.Declaration.Type is not SimpleNameSyntax leafNode + || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + + var symbol = field.Initializer == null ? "-->" : "o->"; + var fieldIdentifier = field.Identifier.ToString(); + var leafName = TypeNameText.From(leafNode); + var rootName = TypeNameText.From(rootNode); + AddRelationship(leafName, rootName, symbol, fieldIdentifier); + } - public void AddAssociationFrom(PropertyDeclarationSyntax node) - { - if (node.Type is not SimpleNameSyntax leafNode - || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; - - var symbol = node.Initializer == null ? "-->" : "o->"; - var nodeIdentifier = node.Identifier.ToString(); - var leafName = TypeNameText.From(leafNode); - var rootName = TypeNameText.From(rootNode); - AddRelationship(leafName, rootName, symbol, nodeIdentifier); - } + public void AddAssociationFrom(PropertyDeclarationSyntax node) + { + if (node.Type is not SimpleNameSyntax leafNode + || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + + var symbol = node.Initializer == null ? "-->" : "o->"; + var nodeIdentifier = node.Identifier.ToString(); + var leafName = TypeNameText.From(leafNode); + var rootName = TypeNameText.From(rootNode); + AddRelationship(leafName, rootName, symbol, nodeIdentifier); + } - public void AddAssociationFrom(ParameterSyntax node, RecordDeclarationSyntax parent) - { - if (node.Type is not SimpleNameSyntax leafNode - || parent is not BaseTypeDeclarationSyntax rootNode) return; - - var symbol = node.Default == null ? "-->" : "o->"; - var nodeIdentifier = node.Identifier.ToString(); - var leafName = TypeNameText.From(leafNode); - var rootName = TypeNameText.From(rootNode); - AddRelationship(leafName, rootName, symbol, nodeIdentifier); - } + public void AddAssociationFrom(ParameterSyntax node, RecordDeclarationSyntax parent) + { + if (node.Type is not SimpleNameSyntax leafNode + || parent is not BaseTypeDeclarationSyntax rootNode) return; + + var symbol = node.Default == null ? "-->" : "o->"; + var nodeIdentifier = node.Identifier.ToString(); + var leafName = TypeNameText.From(leafNode); + var rootName = TypeNameText.From(rootNode); + AddRelationship(leafName, rootName, symbol, nodeIdentifier); + } - public void AddAssociationFrom(PropertyDeclarationSyntax node, PlantUmlAssociationAttribute attribute) - { - if (node.Parent is not BaseTypeDeclarationSyntax rootNode) return; - var leafName = GetLeafName(attribute.Name, node.Type); - if (leafName is null) { return; } - var rootName = TypeNameText.From(rootNode); - AddeRationship(attribute, leafName, rootName); + public void AddAssociationFrom(PropertyDeclarationSyntax node, PlantUmlAssociationAttribute attribute) + { + if (node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + var leafName = GetLeafName(attribute.Name, node.Type); + if (leafName is null) { return; } + var rootName = TypeNameText.From(rootNode); + AddeRationship(attribute, leafName, rootName); - } + } - public void AddAssociationFrom(MethodDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) - { - if (node.Parent is not BaseTypeDeclarationSyntax rootNode) return; - var leafName = GetLeafName(attribute.Name, parameter.Type); - if (leafName is null) { return; } - var rootName = TypeNameText.From(rootNode); - AddeRationship(attribute, leafName, rootName); - } + public void AddAssociationFrom(MethodDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) + { + if (node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + var leafName = GetLeafName(attribute.Name, parameter.Type); + if (leafName is null) { return; } + var rootName = TypeNameText.From(rootNode); + AddeRationship(attribute, leafName, rootName); + } - public void AddAssociationFrom(RecordDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) - { - if (node is not BaseTypeDeclarationSyntax rootNode) { return; } - var leafName = GetLeafName(attribute.Name, parameter.Type); - if (leafName is null) { return; } - var rootName = TypeNameText.From(rootNode); - AddeRationship(attribute, leafName, rootName); - } + public void AddAssociationFrom(RecordDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) + { + if (node is not BaseTypeDeclarationSyntax rootNode) { return; } + var leafName = GetLeafName(attribute.Name, parameter.Type); + if (leafName is null) { return; } + var rootName = TypeNameText.From(rootNode); + AddeRationship(attribute, leafName, rootName); + } - public void AddAssociationFrom(ConstructorDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) - { - if (node.Parent is not BaseTypeDeclarationSyntax rootNode) { return; } - var leafName = GetLeafName(attribute.Name, parameter.Type); - if (leafName is null) { return; } - var rootName = TypeNameText.From(rootNode); - AddeRationship(attribute, leafName, rootName); - } + public void AddAssociationFrom(ConstructorDeclarationSyntax node, ParameterSyntax parameter, PlantUmlAssociationAttribute attribute) + { + if (node.Parent is not BaseTypeDeclarationSyntax rootNode) { return; } + var leafName = GetLeafName(attribute.Name, parameter.Type); + if (leafName is null) { return; } + var rootName = TypeNameText.From(rootNode); + AddeRationship(attribute, leafName, rootName); + } - public void AddAssociationFrom(FieldDeclarationSyntax node, PlantUmlAssociationAttribute attribute) - { - if (node.Parent is not BaseTypeDeclarationSyntax rootNode) { return; } - var leafName = GetLeafName(attribute.Name, node.Declaration.Type); - if(leafName is null) { return; } - var rootName = TypeNameText.From(rootNode); - AddeRationship(attribute, leafName, rootName); - } + public void AddAssociationFrom(FieldDeclarationSyntax node, PlantUmlAssociationAttribute attribute) + { + if (node.Parent is not BaseTypeDeclarationSyntax rootNode) { return; } + var leafName = GetLeafName(attribute.Name, node.Declaration.Type); + if(leafName is null) { return; } + var rootName = TypeNameText.From(rootNode); + AddeRationship(attribute, leafName, rootName); + } - private static TypeNameText GetLeafName(string attributeName, TypeSyntax typeSyntax) + private static TypeNameText GetLeafName(string attributeName, TypeSyntax typeSyntax) + { + if (!string.IsNullOrWhiteSpace(attributeName)) { - if (!string.IsNullOrWhiteSpace(attributeName)) - { - return new TypeNameText() { Identifier = attributeName }; - } - else if (typeSyntax is SimpleNameSyntax simpleNode) - { - return TypeNameText.From(simpleNode); - } - return null; - + return new TypeNameText() { Identifier = attributeName }; } - - private void AddeRationship(PlantUmlAssociationAttribute attribute, TypeNameText leafName, TypeNameText rootName) + else if (typeSyntax is SimpleNameSyntax simpleNode) { - var symbol = string.IsNullOrEmpty(attribute.Association) ? "--" : attribute.Association; - items.Add(new Relationship(rootName, leafName, symbol, attribute.RootLabel, attribute.LeafLabel, attribute.Label)); + return TypeNameText.From(simpleNode); } + return null; + + } - private void AddRelationship(TypeNameText leafName, TypeNameText rootName, string symbol, string nodeIdentifier) - { - items.Add(new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments)); - } + 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)); + } - public IEnumerator GetEnumerator() - { - return items.GetEnumerator(); - } + private void AddRelationship(TypeNameText leafName, TypeNameText rootName, string symbol, string nodeIdentifier) + { + items.Add(new Relationship(rootName, leafName, symbol, "", nodeIdentifier + leafName.TypeArguments)); + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index d88d605..988ee25 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -1,76 +1,75 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace PlantUmlClassDiagramGenerator.Library +namespace PlantUmlClassDiagramGenerator.Library; + +public class TypeNameText { - public class TypeNameText - { - public string Identifier { get; set; } + public string Identifier { get; set; } - public string TypeArguments { get; set; } - - public static TypeNameText From(SimpleNameSyntax syntax) + public string TypeArguments { get; set; } + + public static TypeNameText From(SimpleNameSyntax syntax) + { + var identifier = syntax.Identifier.Text; + var typeArgs = string.Empty; + if (syntax is GenericNameSyntax genericName && genericName.TypeArgumentList != null) { - var identifier = syntax.Identifier.Text; - var typeArgs = string.Empty; - if (syntax is GenericNameSyntax genericName && genericName.TypeArgumentList != null) - { - var count = genericName.TypeArgumentList.Arguments.Count; - identifier = $"\"{identifier}`{count}\""; - typeArgs = "<" + string.Join(",", genericName.TypeArgumentList.Arguments) + ">"; - } - else if (identifier.StartsWith('@')) - { - identifier = $"\"{identifier}\""; - } - return new TypeNameText - { - Identifier = identifier, - TypeArguments = typeArgs - }; + var count = genericName.TypeArgumentList.Arguments.Count; + identifier = $"\"{identifier}`{count}\""; + typeArgs = "<" + string.Join(",", genericName.TypeArgumentList.Arguments) + ">"; } - - public static TypeNameText From(GenericNameSyntax syntax) + else if (identifier.StartsWith('@')) { - int paramCount = syntax.TypeArgumentList.Arguments.Count; - string[] parameters = new string[paramCount]; - if (paramCount > 1) - { - for (int i = 0; i < paramCount; i++) - { - parameters[i] = $"T{i + 1}"; - } + identifier = $"\"{identifier}\""; + } + return new TypeNameText + { + Identifier = identifier, + TypeArguments = typeArgs + }; + } - } - else + public static TypeNameText From(GenericNameSyntax syntax) + { + int paramCount = syntax.TypeArgumentList.Arguments.Count; + string[] parameters = new string[paramCount]; + if (paramCount > 1) + { + for (int i = 0; i < paramCount; i++) { - parameters[0] = "T"; + parameters[i] = $"T{i + 1}"; } - return new TypeNameText - { - Identifier = $"\"{syntax.Identifier.Text}`{paramCount}\"", - TypeArguments = "<" + string.Join(",", parameters) + ">", - }; + + } + else + { + parameters[0] = "T"; } + return new TypeNameText + { + Identifier = $"\"{syntax.Identifier.Text}`{paramCount}\"", + TypeArguments = "<" + string.Join(",", parameters) + ">", + }; + } - public static TypeNameText From(BaseTypeDeclarationSyntax syntax) + public static TypeNameText From(BaseTypeDeclarationSyntax syntax) + { + var identifier = syntax.Identifier.Text; + var typeArgs = string.Empty; + if (syntax is TypeDeclarationSyntax typeDeclaration && typeDeclaration.TypeParameterList != null) { - var identifier = syntax.Identifier.Text; - var typeArgs = string.Empty; - if (syntax is TypeDeclarationSyntax typeDeclaration && typeDeclaration.TypeParameterList != null) - { - var count = typeDeclaration.TypeParameterList.Parameters.Count; - identifier = $"\"{identifier}`{count}\""; - typeArgs = "<" + string.Join(",", typeDeclaration.TypeParameterList.Parameters) + ">"; - } - else if (identifier.StartsWith('@')) - { - identifier = $"\"{identifier}\""; - } - return new TypeNameText - { - Identifier = identifier, - TypeArguments = typeArgs - }; + var count = typeDeclaration.TypeParameterList.Parameters.Count; + identifier = $"\"{identifier}`{count}\""; + typeArgs = "<" + string.Join(",", typeDeclaration.TypeParameterList.Parameters) + ">"; } + else if (identifier.StartsWith('@')) + { + identifier = $"\"{identifier}\""; + } + return new TypeNameText + { + Identifier = identifier, + TypeArguments = typeArgs + }; } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs b/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs index 9e4544d..5fe937b 100644 --- a/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs +++ b/src/PlantUmlClassDiagramGenerator/ExcludeFileFilter.cs @@ -1,40 +1,38 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -namespace PlantUmlClassDiagramGenerator +namespace PlantUmlClassDiagramGenerator; + +public class ExcludeFileFilter { - public class ExcludeFileFilter + public static IEnumerable GetFilesToProcess(IEnumerable files, IList excludePaths, string inputRoot) { - public static IEnumerable GetFilesToProcess(IEnumerable files, IList excludePaths, string inputRoot) - { - return files.Where(f => !IsFileExcluded(f, excludePaths, inputRoot)); - } + return files.Where(f => !IsFileExcluded(f, excludePaths, inputRoot)); + } + + private static bool IsFileExcluded(string inputFile, IEnumerable excludePaths, string inputRoot) + { + bool isExcluded = excludePaths.Any(excludePath => IsFileExcluded(inputFile, excludePath, inputRoot)); - private static bool IsFileExcluded(string inputFile, IEnumerable excludePaths, string inputRoot) + if (isExcluded) { - bool isExcluded = excludePaths.Any(excludePath => IsFileExcluded(inputFile, excludePath, inputRoot)); + Console.WriteLine($"Skipped \"{inputFile}\"..."); + } - if (isExcluded) - { - Console.WriteLine($"Skipped \"{inputFile}\"..."); - } + return isExcluded; + } - return isExcluded; + private static bool IsFileExcluded(string inputFile, string excludePath, string inputRoot) + { + if (excludePath.StartsWith("**/")) + { + return inputFile.Split('\\', '/').Any(x => x.StartsWith(excludePath[3..])); } - - private static bool IsFileExcluded(string inputFile, string excludePath, string inputRoot) + else { - if (excludePath.StartsWith("**/")) - { - return inputFile.Split('\\', '/').Any(x => x.StartsWith(excludePath[3..])); - } - else - { - string fullPath = PathHelper.CombinePath(inputRoot, excludePath); - return inputFile.StartsWith(fullPath, StringComparison.InvariantCultureIgnoreCase); - } + string fullPath = PathHelper.CombinePath(inputRoot, excludePath); + return inputFile.StartsWith(fullPath, StringComparison.InvariantCultureIgnoreCase); } } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/PathHelper.cs b/src/PlantUmlClassDiagramGenerator/PathHelper.cs index fa341c0..576baf2 100644 --- a/src/PlantUmlClassDiagramGenerator/PathHelper.cs +++ b/src/PlantUmlClassDiagramGenerator/PathHelper.cs @@ -1,14 +1,13 @@ using System.IO; -namespace PlantUmlClassDiagramGenerator +namespace PlantUmlClassDiagramGenerator; + +public static class PathHelper { - public static class PathHelper + public static string CombinePath(string first, string second) { - public static string CombinePath(string first, string second) - { - return first.TrimEnd(Path.DirectorySeparatorChar) - + Path.DirectorySeparatorChar - + second.TrimStart(Path.DirectorySeparatorChar); - } + return first.TrimEnd(Path.DirectorySeparatorChar) + + Path.DirectorySeparatorChar + + second.TrimStart(Path.DirectorySeparatorChar); } } \ No newline at end of file diff --git a/src/PlantUmlClassDiagramGenerator/Program.cs b/src/PlantUmlClassDiagramGenerator/Program.cs index 779ece1..59dbaae 100644 --- a/src/PlantUmlClassDiagramGenerator/Program.cs +++ b/src/PlantUmlClassDiagramGenerator/Program.cs @@ -8,272 +8,271 @@ using PlantUmlClassDiagramGenerator.Library; using System.Runtime.InteropServices; -namespace PlantUmlClassDiagramGenerator +namespace PlantUmlClassDiagramGenerator; + +class Program { - class Program + enum OptionType + { + Value, + Switch + } + + static readonly Dictionary options = new() { - enum OptionType + ["-dir"] = OptionType.Switch, + ["-public"] = OptionType.Switch, + ["-ignore"] = OptionType.Value, + ["-excludePaths"] = OptionType.Value, + ["-createAssociation"] = OptionType.Switch, + ["-allInOne"] = OptionType.Switch, + ["-attributeRequired"] = OptionType.Switch, + ["-excludeUmlBeginEndTags"] = OptionType.Switch + }; + + static int Main(string[] args) + { + Dictionary parameters = MakeParameters(args); + if (!parameters.ContainsKey("in")) { - Value, - Switch + Console.WriteLine("Specify a source file name or directory name."); + return -1; } - - static readonly Dictionary options = new() + if (parameters.ContainsKey("-dir")) { - ["-dir"] = OptionType.Switch, - ["-public"] = OptionType.Switch, - ["-ignore"] = OptionType.Value, - ["-excludePaths"] = OptionType.Value, - ["-createAssociation"] = OptionType.Switch, - ["-allInOne"] = OptionType.Switch, - ["-attributeRequired"] = OptionType.Switch, - ["-excludeUmlBeginEndTags"] = OptionType.Switch - }; - - static int Main(string[] args) + if (!GeneratePlantUmlFromDir(parameters)) { return -1; } + } + else { - Dictionary parameters = MakeParameters(args); - if (!parameters.ContainsKey("in")) - { - 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; + if (!GeneratePlantUmlFromFile(parameters)) { return -1; } } + return 0; + } - private static bool GeneratePlantUmlFromFile(Dictionary parameters) + private static bool GeneratePlantUmlFromFile(Dictionary parameters) + { + var inputFileName = parameters["in"]; + if (!File.Exists(inputFileName)) { - 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 = CombinePath(Path.GetDirectoryName(inputFileName), - Path.GetFileNameWithoutExtension(inputFileName) + ".puml"); - } - + Console.WriteLine($"\"{inputFileName}\" does not exist."); + return false; + } + string outputFileName; + if (parameters.TryGetValue("out", out string value)) + { + outputFileName = value; 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); - - 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); + var outdir = Path.GetDirectoryName(outputFileName); + Directory.CreateDirectory(outdir); } catch (Exception e) { Console.WriteLine(e); return false; } - return true; + } + else + { + outputFileName = CombinePath(Path.GetDirectoryName(inputFileName), + Path.GetFileNameWithoutExtension(inputFileName) + ".puml"); } - private static bool GeneratePlantUmlFromDir(Dictionary parameters) + try { - var inputRoot = parameters["in"]; - if (!Directory.Exists(inputRoot)) - { - Console.WriteLine($"Directory \"{inputRoot}\" does not exist."); - return false; - } + 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); - // 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; - } - } + 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; + } + + private static bool GeneratePlantUmlFromDir(Dictionary parameters) + { + var inputRoot = parameters["in"]; + if (!Directory.Exists(inputRoot)) + { + Console.WriteLine($"Directory \"{inputRoot}\" does not exist."); + return false; + } - var excludePaths = new List(); - var pumlexclude = CombinePath(inputRoot, ".pumlexclude"); - if (File.Exists(pumlexclude)) + // Use GetFullPath to fully support relative paths. + var outputRoot = Path.GetFullPath(inputRoot); + if (parameters.TryGetValue("out", out string outValue)) + { + outputRoot = outValue; + try { - excludePaths = File - .ReadAllLines(pumlexclude) - .Where(path => !string.IsNullOrWhiteSpace(path)) - .Select(path => path.Trim()) - .ToList(); + Directory.CreateDirectory(outputRoot); } - if (parameters.TryGetValue("-excludePaths", out string excludePathValue)) + catch (Exception e) { - var splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; - excludePaths.AddRange(excludePathValue.Split(',', splitOptions)); + Console.WriteLine(e); + return false; } + } - var excludeUmlBeginEndTags = parameters.ContainsKey("-excludeUmlBeginEndTags"); - var files = Directory.EnumerateFiles(inputRoot, "*.cs", SearchOption.AllDirectories); + 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 includeRefs = new StringBuilder(); - if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@startuml"); + var excludeUmlBeginEndTags = parameters.ContainsKey("-excludeUmlBeginEndTags"); + var files = Directory.EnumerateFiles(inputRoot, "*.cs", SearchOption.AllDirectories); - var error = false; - var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); - foreach (var inputFile in filesToProcess) + var includeRefs = new StringBuilder(); + if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@startuml"); + + var error = false; + var filesToProcess = ExcludeFileFilter.GetFilesToProcess(files, excludePaths, inputRoot); + foreach (var inputFile in filesToProcess) + { + Console.WriteLine($"Processing \"{inputFile}\"..."); + try { - 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"); + 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 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); - gen.Generate(root); - } + 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); + gen.Generate(root); + } - if (parameters.ContainsKey("-allInOne")) + if (parameters.ContainsKey("-allInOne")) + { + var lines = File.ReadAllLines(outputFile); + if (!excludeUmlBeginEndTags) { - var lines = File.ReadAllLines(outputFile); - if (!excludeUmlBeginEndTags) - { - lines = lines.Skip(1).SkipLast(1).ToArray(); - } - foreach (string line in lines) - { - includeRefs.AppendLine(line); - } + lines = lines.Skip(1).SkipLast(1).ToArray(); } - else + foreach (string line in lines) { - var newRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @".\" : @"."; - includeRefs.AppendLine("!include " + outputFile.Replace(outputRoot, newRoot)); + includeRefs.AppendLine(line); } } - catch (Exception e) + else { - Console.WriteLine(e); - error = true; + var newRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @".\" : @"."; + includeRefs.AppendLine("!include " + outputFile.Replace(outputRoot, newRoot)); } } - if (!excludeUmlBeginEndTags) includeRefs.AppendLine("@enduml"); - File.WriteAllText(CombinePath(outputRoot, "include.puml"), includeRefs.ToString()); - - if (error) + catch (Exception e) { - Console.WriteLine("There were files that could not be processed."); - return false; + Console.WriteLine(e); + error = true; } - return true; } + 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) + private static Accessibilities GetIgnoreAccessibilities(Dictionary parameters) + { + var ignoreAcc = Accessibilities.None; + if (parameters.ContainsKey("-public")) { - 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)) + 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) { - var ignoreItems = value.Split(','); - foreach (var item in ignoreItems) + if (Enum.TryParse(item, true, out Accessibilities acc)) { - if (Enum.TryParse(item, true, out Accessibilities acc)) - { - ignoreAcc |= acc; - } + ignoreAcc |= acc; } } - return ignoreAcc; } + return ignoreAcc; + } - private static Dictionary MakeParameters(string[] args) + private static Dictionary MakeParameters(string[] args) + { + var currentKey = ""; + var parameters = new Dictionary(); + + foreach (var arg in args) { - var currentKey = ""; - var parameters = new Dictionary(); + if (currentKey != string.Empty) + { + parameters.Add(currentKey, arg); + currentKey = ""; + continue; + } - foreach (var arg in args) + if (options.TryGetValue(arg, out OptionType value)) { - if (currentKey != string.Empty) + if (value == OptionType.Value) { - parameters.Add(currentKey, arg); - currentKey = ""; - continue; + currentKey = arg; } - - if (options.TryGetValue(arg, out OptionType value)) + else { - if (value == OptionType.Value) - { - currentKey = arg; - } - else - { - parameters.Add(arg, string.Empty); - } + parameters.Add(arg, string.Empty); } - - parameters.TryAdd("in", arg); - parameters.TryAdd("out", arg); } - return parameters; - } - private static string CombinePath(string first, string second) - { - return PathHelper.CombinePath(first, second); + parameters.TryAdd("in", arg); + parameters.TryAdd("out", arg); } + return parameters; + } + + private static string CombinePath(string first, string second) + { + return PathHelper.CombinePath(first, second); } } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index 124ae2a..d16b3c6 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -5,263 +5,262 @@ using System.IO; using PlantUmlClassDiagramGenerator.Library; -namespace PlantUmlClassDiagramGeneratorTest +namespace PlantUmlClassDiagramGeneratorTest; + +[TestClass] +public partial class ClassDiagramGeneratorTest { - [TestClass] - public partial class ClassDiagramGeneratorTest + [TestMethod] + public void GenerateTestAll() { - [TestMethod] - public void GenerateTestAll() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " "); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "all.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } + var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); - [TestMethod] - public void GenerateTestPublic() + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", - Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "public.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " "); + gen.Generate(root); } - [TestMethod] - public void GenerateTestWithoutPrivate() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "all.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestPublic() + { + var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testdata", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutPrivate.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", + Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal); + gen.Generate(root); } - [TestMethod] - public void GenerateTestGenericsTypes() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "public.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestWithoutPrivate() + { + var code = File.ReadAllText(Path.Combine("testdata", "InputClasses.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testdata", "GenericsType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "genericsType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private); + gen.Generate(root); } - - [TestMethod] - public void NullableTestNullableTypes() + + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutPrivate.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestGenericsTypes() + { + var code = File.ReadAllText(Path.Combine("testdata", "GenericsType.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "NullableType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "nullableType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal); + gen.Generate(root); } - [TestMethod] - public void GenerateTestAtPrefixType() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "genericsType.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void NullableTestNullableTypes() + { + var code = File.ReadAllText(Path.Combine("testData", "NullableType.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "AtPrefixType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AtPrefixType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal); + gen.Generate(root); } - private static string ConvertNewLineCode(string text, string newline) + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "nullableType.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestAtPrefixType() + { + var code = File.ReadAllText(Path.Combine("testData", "AtPrefixType.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var reg = EndLineRegex(); - return reg.Replace(text, newline); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal, true); + gen.Generate(root); } - [TestMethod] - public void GenerateTestRecordTypes() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AtPrefixType.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + private static string ConvertNewLineCode(string text, string newline) + { + var reg = EndLineRegex(); + return reg.Replace(text, newline); + } + + [TestMethod] + public void GenerateTestRecordTypes() + { + var code = File.ReadAllText(Path.Combine("testData", "RecordType.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "RecordType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "RecordType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal); + gen.Generate(root); } - [TestMethod] - public void GenerateTestAttributes() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "RecordType.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestAttributes() + { + var code = File.ReadAllText(Path.Combine("testData", "Attributes.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "Attributes.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "Attributes.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal + | Accessibilities.Protected | Accessibilities.ProtectedInternal); + gen.Generate(root); } - [TestMethod] - public void GenerateTestAttributeRequired() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "Attributes.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestAttributeRequired() + { + var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, true); + gen.Generate(root); } - [TestMethod] - public void GenerateTestNotAttributeRequired() + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AttributeRequired.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void GenerateTestNotAttributeRequired() + { + var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "NotAttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); + gen.Generate(root); } - [TestMethod] - public void GenerateTestWithoutUmlStartEnd() + + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "NotAttributeRequired.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void GenerateTestWithoutUmlStartEnd() + { + var code = File.ReadAllText(Path.Combine("testData", "inputClasses.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "inputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private, true, false, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutStartEndUml.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private, true, false, true); + gen.Generate(root); } - [TestMethod] - public void GenerateTestDefaultModifierType() + + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutStartEndUml.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); + } + [TestMethod] + public void GenerateTestDefaultModifierType() + { + var code = File.ReadAllText(Path.Combine("testData", "DefaultModifierType.cs")); + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot(); + + var output = new StringBuilder(); + using (var writer = new StringWriter(output)) { - var code = File.ReadAllText(Path.Combine("testData", "DefaultModifierType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "DefaultModifierType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); + gen.Generate(root); } - [System.Text.RegularExpressions.GeneratedRegex("\r\n|\r|\n")] - private static partial System.Text.RegularExpressions.Regex EndLineRegex(); + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "DefaultModifierType.puml")), Environment.NewLine); + var actual = output.ToString(); + Console.Write(actual); + Assert.AreEqual(expected, actual); } + + [System.Text.RegularExpressions.GeneratedRegex("\r\n|\r|\n")] + private static partial System.Text.RegularExpressions.Regex EndLineRegex(); } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs index 65e17df..552f415 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs @@ -5,58 +5,57 @@ using PlantUmlClassDiagramGenerator; using System.IO; -namespace PlantUmlClassDiagramGeneratorTest.UnitTests +namespace PlantUmlClassDiagramGeneratorTest.UnitTests; + +[TestClass] +public class ExcludeFileFilterTest { - [TestClass] - public class ExcludeFileFilterTest - { - private ExcludeFileFilter testObject; + private ExcludeFileFilter testObject; - private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; - private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); + private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; + private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); - private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); - private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); - private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); - private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); - private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); - private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); - private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); + private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); + private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); + private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); + private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); + private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); + private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); + private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); - private readonly string[] TestFiles = - {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; + private readonly string[] TestFiles = + {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; - [TestInitialize] - public void TestInitialize() - { - testObject = new ExcludeFileFilter(); - } + [TestInitialize] + public void TestInitialize() + { + testObject = new ExcludeFileFilter(); + } - [DataTestMethod] - [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] - [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] - [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] - [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] - [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] - [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] - public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) + [DataTestMethod] + [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] + [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] + [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] + [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] + [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] + [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] + public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) + { + if (Environment.OSVersion.Platform == PlatformID.Unix) { - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); - } - // Act - List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); - - // Assert - string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); - CollectionAssert.AreEquivalent(expected, result); + excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); } + // Act + List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); + // Assert + string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); + CollectionAssert.AreEquivalent(expected, result); + } - private static string[] GetByIndices(string[] array, params int[] indices) - { - return indices.Select(i => array[i]).ToArray(); - } + + private static string[] GetByIndices(string[] array, params int[] indices) + { + return indices.Select(i => array[i]).ToArray(); } } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs index 1c7b069..49b7056 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs @@ -4,23 +4,22 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest +namespace PlantUmlClassDiagramGeneratorTest; + +class @ClassA { - class @ClassA - { - public @IList<@string> @Strings { get; } = new @List<@string>(); - public @Type1 @Prop1 { get; set; } - public @Type2 @field1; - } + public @IList<@string> @Strings { get; } = new @List<@string>(); + public @Type1 @Prop1 { get; set; } + public @Type2 @field1; +} - class @Type1 - { - public @int @value1 { get; set; } - } +class @Type1 +{ + public @int @value1 { get; set; } +} - class @Type2 - { - public @string @string1 { get; set; } - public @ExternalType @Prop2 { get; set; } - } +class @Type2 +{ + public @string @string1 { get; set; } + public @ExternalType @Prop2 { get; set; } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs index 248c54f..f7075f9 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs @@ -1,102 +1,91 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using PlantUmlClassDiagramGenerator.Attributes; +using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData -{ - - class ClassA - { - [PlantUmlAssociation(Association ="*--")] - public string Prop1 { get; set; } - [PlantUmlIgnore] - public int Prop2 { get; set; } - } - - [PlantUmlDiagram] - class ClassB - { - [PlantUmlAssociation(Association = "*--")] - public string Prop1 { get; set; } - [PlantUmlIgnore] - public int Prop2 { get; set; } - } +namespace PlantUmlClassDiagramGeneratorTest.testData; - interface IInterfaceA - { - [PlantUmlAssociation(Association ="--",Label ="double property")] - double Num1 { get; } - double Num2 { get; } - } - [PlantUmlDiagram] - interface IInterfaceB - { - [PlantUmlAssociation(Association = "--", Label = "double property")] - double Num1 { get; } - double Num2 { get; } - } +class ClassA +{ + [PlantUmlAssociation(Association ="*--")] + public string Prop1 { get; set; } + [PlantUmlIgnore] + public int Prop2 { get; set; } +} - record RecordA - { - public string Prop1 { get; } - public string Prop2 { get; } - public RecordA(string prop1, string prop2) => (Prop1, Prop2) = (prop1, prop2); - - } +[PlantUmlDiagram] +class ClassB +{ + [PlantUmlAssociation(Association = "*--")] + public string Prop1 { get; set; } + [PlantUmlIgnore] + public int Prop2 { get; set; } +} - [PlantUmlDiagram] - record RecordB - { - public string Prop1 { get; } - public string Prop2 { get; } - public RecordB(string prop1, string prop2) => (Prop1, Prop2) = (prop1, prop2); - } - - struct StructA - { - private RecordA field1; - public StructA([PlantUmlAssociation(Association = "-->", Label = "use")] RecordA field1) - { - this.field1 = field1; - } - } +interface IInterfaceA +{ + [PlantUmlAssociation(Association ="--",Label ="double property")] + double Num1 { get; } + double Num2 { get; } +} - [PlantUmlDiagram] - struct StructB - { - private RecordB field1; - public StructB([PlantUmlAssociation(Association ="-->",Label ="use")]RecordB field1) - { - this.field1 = field1; - } - } +[PlantUmlDiagram] +interface IInterfaceB +{ + [PlantUmlAssociation(Association = "--", Label = "double property")] + double Num1 { get; } + double Num2 { get; } +} - record struct RecordStructA(int X,int Y); +record RecordA +{ + public string Prop1 { get; } + public string Prop2 { get; } + public RecordA(string prop1, string prop2) => (Prop1, Prop2) = (prop1, prop2); + +} - [PlantUmlDiagram] - record struct RecordStructB(int X,int Y); +[PlantUmlDiagram] +record RecordB +{ + public string Prop1 { get; } + public string Prop2 { get; } + public RecordB(string prop1, string prop2) => (Prop1, Prop2) = (prop1, prop2); +} - enum EnumA +struct StructA +{ + private RecordA field1; + public StructA([PlantUmlAssociation(Association = "-->", Label = "use")] RecordA field1) { - A, - B, - C + this.field1 = field1; } +} - [PlantUmlDiagram] - enum EnumB +[PlantUmlDiagram] +struct StructB +{ + private RecordB field1; + public StructB([PlantUmlAssociation(Association ="-->",Label ="use")]RecordB field1) { - A, - B, - C + this.field1 = field1; } +} +record struct RecordStructA(int X,int Y); +[PlantUmlDiagram] +record struct RecordStructB(int X,int Y); +enum EnumA +{ + A, + B, + C +} - +[PlantUmlDiagram] +enum EnumB +{ + A, + B, + C } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs index 4562f4a..e926bc4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs @@ -5,74 +5,73 @@ using System.Threading.Tasks; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData -{ +namespace PlantUmlClassDiagramGeneratorTest.testData; - class Parameters - { - public string A { get; set; } - public string B { get; set; } - } - class MyClass - { - [PlantUmlAssociation(Name = "Item", Association = "o--", LeafLabel = "0..*", Label = "Items")] - public IList Items { get; set; } +class Parameters +{ + public string A { get; set; } + public string B { get; set; } +} - [PlantUmlAssociation(Name = "IItem", Association = "*--", RootLabel = "1", Label = "ItemDictionary", LeafLabel = "0..*")] - public IDictionary ItemDictionary { get; set; } = new Dictionary(); +class MyClass +{ + [PlantUmlAssociation(Name = "Item", Association = "o--", LeafLabel = "0..*", Label = "Items")] + public IList Items { get; set; } - [PlantUmlIgnore] - public string HiddenProp { get; set; } + [PlantUmlAssociation(Name = "IItem", Association = "*--", RootLabel = "1", Label = "ItemDictionary", LeafLabel = "0..*")] + public IDictionary ItemDictionary { get; set; } = new Dictionary(); - [PlantUmlIgnoreAssociation] - public IReadOnlyCollection ReadOnlyItems { get; } + [PlantUmlIgnore] + public string HiddenProp { get; set; } - public void Run([PlantUmlAssociation(Association = "..>", Label = "use")] Parameters p) - { - Console.WriteLine($"{p.A},{p.B}"); - } + [PlantUmlIgnoreAssociation] + public IReadOnlyCollection ReadOnlyItems { get; } - private ILogger logger; - public MyClass([PlantUmlAssociation(Association = "..>", Label = "Injection")] ILogger logger) - { - this.logger = logger; - } + public void Run([PlantUmlAssociation(Association = "..>", Label = "use")] Parameters p) + { + Console.WriteLine($"{p.A},{p.B}"); } - struct MyStruct + private ILogger logger; + public MyClass([PlantUmlAssociation(Association = "..>", Label = "Injection")] ILogger logger) { - [PlantUmlAssociation(Name = "int", Association = "o--", LeafLabel = "0..*", Label = "intCollection:List")] - public IList intCollection; - - [PlantUmlAssociation(Name = "Parameters", Association = "-l->")] - public MyStruct(Parameters p) - { - } + this.logger = logger; } +} - record MyRecord(string name, [PlantUmlAssociation(Association = "o--")] Settings s); +struct MyStruct +{ + [PlantUmlAssociation(Name = "int", Association = "o--", LeafLabel = "0..*", Label = "intCollection:List")] + public IList intCollection; - record struct MyStructRecord + [PlantUmlAssociation(Name = "Parameters", Association = "-l->")] + public MyStruct(Parameters p) { - [PlantUmlAssociation(Name="string", Association = "o--",LeafLabel="Name")] - public string Name { get; init; } - } +} +record MyRecord(string name, [PlantUmlAssociation(Association = "o--")] Settings s); - [PlantUmlIgnore] - class HiddenClass - { - public string PropA { get; set; } - } +record struct MyStructRecord +{ + [PlantUmlAssociation(Name="string", Association = "o--",LeafLabel="Name")] + public string Name { get; init; } + +} - class ClassA + +[PlantUmlIgnore] +class HiddenClass +{ + public string PropA { get; set; } +} + +class ClassA +{ + private ILogger logger; + public ClassA([PlantUmlAssociation(Name = "\"ILogger\"" Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) { - private ILogger logger; - public ClassA([PlantUmlAssociation(Name = "\"ILogger\"" Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) - { - this.logger = logger; - } + this.logger = logger; } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs index bd1a18f..6cbd797 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs @@ -1,22 +1,21 @@ -namespace PlantUmlClassDiagramGeneratorTest +namespace PlantUmlClassDiagramGeneratorTest; + +public class CurlyBrackets { - public class CurlyBrackets - { - public string openingBracket = @" + public string openingBracket = @" { "; - public string openingBrackets = @" + public string openingBrackets = @" {{ "; - public string closingBracket = @" + public string closingBracket = @" } "; - public string closingBrackets = @" + public string closingBrackets = @" }} "; - public string bothBrackets = @" + public string bothBrackets = @" {{ }} "; - } } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs index ef42c76..0244b1f 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs @@ -4,33 +4,32 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData +namespace PlantUmlClassDiagramGeneratorTest.testData; + +class ClassA { - class ClassA - { - int PropA { get; } - public int PropB { get; } - protected internal int PropD { get; } - void MethodA() { } - private int MethodB() => 1; - public ClassA() { } - ClassA() { } - } + int PropA { get; } + public int PropB { get; } + protected internal int PropD { get; } + void MethodA() { } + private int MethodB() => 1; + public ClassA() { } + ClassA() { } +} - struct StructA - { - string PropA { get; } - static string PropB { get; } - private string PropC { get; } - static void MethodA() { } - private static int MethodB() => 2; - public StructA() { } - StructA() { } - } +struct StructA +{ + string PropA { get; } + static string PropB { get; } + private string PropC { get; } + static void MethodA() { } + private static int MethodB() => 2; + public StructA() { } + StructA() { } +} - interface Interface - { - int PropA { get; } - void MethodA(); - } +interface Interface +{ + int PropA { get; } + void MethodA(); } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs index eb80f5c..230cc8a 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs @@ -4,48 +4,47 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest -{ - class GenericsType - { - public object Value { get; } - } - - class GenericsType - { - public T Value { get; } - } - - class GenericsType - { - public T1 Value1 { get; } - public T2 Value2; - } - - class SubClass : GenericsType - { - public string Value1 { get; } - public int Value2; - } - - class SubClass: GenericsType, T> - { - public GenericsType Value1 { get; } - public T Value2 { get; } - } - - class SubClassX : BaseClass - { - public GenericsType Gt { get; set; } - } - - class BaseClass - { - public Tx X { get; set; } - } - - partial class GenericsType - { - public int Number { get; set; } - } +namespace PlantUmlClassDiagramGeneratorTest; + +class GenericsType +{ + public object Value { get; } +} + +class GenericsType +{ + public T Value { get; } +} + +class GenericsType +{ + public T1 Value1 { get; } + public T2 Value2; +} + +class SubClass : GenericsType +{ + public string Value1 { get; } + public int Value2; +} + +class SubClass: GenericsType, T> +{ + public GenericsType Value1 { get; } + public T Value2 { get; } +} + +class SubClassX : BaseClass +{ + public GenericsType Gt { get; set; } +} + +class BaseClass +{ + public Tx X { get; set; } +} + +partial class GenericsType +{ + public int Number { get; set; } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs index 1a439a3..46ab3f4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs @@ -2,127 +2,126 @@ using System.Collections.Generic; using System.ComponentModel; -namespace PlantUmlClassDiagramGeneratorTest -{ - public class ClassA - { - private readonly int intField = 100; - private static string strField; - protected double X = 0, Y = 1, Z = 2; - private IList list = new List(); +namespace PlantUmlClassDiagramGeneratorTest; - protected int PropA { get; private set; } - protected internal string PropB { get; protected set; } - internal double PropC { get; } = 3.141592; - public string PropD => "expression-bodied property"; +public class ClassA +{ + private readonly int intField = 100; + private static string strField; + protected double X = 0, Y = 1, Z = 2; + private IList list = new List(); - public ClassA() { } - static ClassA() { strField = "static field"; } + protected int PropA { get; private set; } + protected internal string PropB { get; protected set; } + internal double PropC { get; } = 3.141592; + public string PropD => "expression-bodied property"; - protected virtual void VirtualMethod() { } - public override string ToString() - { - return intField.ToString(); - } + public ClassA() { } + static ClassA() { strField = "static field"; } - public static string StaticMethod() { return strField; } - public void ExpressonBodiedMethod(int x) => x * x; + protected virtual void VirtualMethod() { } + public override string ToString() + { + return intField.ToString(); } - internal abstract class ClassB - { - public ClassA publicA; - public IList listOfA = new IList(); - private int field1; - public abstract int PropA { get; protected set; } + public static string StaticMethod() { return strField; } + public void ExpressonBodiedMethod(int x) => x * x; +} - protected virtual string VirtualMethod() { return "virtual"; } - public abstract string AbstractMethod(int arg1, double arg2); - } +internal abstract class ClassB +{ + public ClassA publicA; + public IList listOfA = new IList(); + private int field1; + public abstract int PropA { get; protected set; } - internal sealed class ClassC : ClassB, INotifyPropertyChanged - { - private static readonly string readonlyField = "ReadOnly"; - public override int PropA { get; protected set; } = 100; - public ClassB PropB { get; set; } + protected virtual string VirtualMethod() { return "virtual"; } + public abstract string AbstractMethod(int arg1, double arg2); +} - public event PropertyChangedEventHandler PropertyChanged; +internal sealed class ClassC : ClassB, INotifyPropertyChanged +{ + private static readonly string readonlyField = "ReadOnly"; + public override int PropA { get; protected set; } = 100; + public ClassB PropB { get; set; } - private void RaisePropertyChanged(string propertyName) => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + public event PropertyChangedEventHandler PropertyChanged; + private void RaisePropertyChanged(string propertyName) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - public override string AbstractMethod(int arg1, double arg2) - { - return readonlyField; - } - protected override string VirtualMethod() - { - return base.VirtualMethod(); - } + public override string AbstractMethod(int arg1, double arg2) + { + return readonlyField; } - public struct Vector + protected override string VirtualMethod() { - public double X { get; } - public double Y { get; } - public double Z { get; } + return base.VirtualMethod(); + } +} - public Vector(double x, double y, double z) - { - X = x; - Y = y; - Z = z; - } +public struct Vector +{ + public double X { get; } + public double Y { get; } + public double Z { get; } - public Vector(Vector source) - : this(source.X, source.Y, source.Z) - { } + public Vector(double x, double y, double z) + { + X = x; + Y = y; + Z = z; + } - public static Vector operator +(Vector a, Vector b) - { - return new Vector( - a.X + b.X, - a.Y + b.Y, - a.Z + b.Z); - } + public Vector(Vector source) + : this(source.X, source.Y, source.Z) + { } - public static Vector operator -(Vector a, Vector b) - { - return new Vector( - a.X - b.X, - a.Y - b.Y, - a.Z - b.Z); - } + public static Vector operator +(Vector a, Vector b) + { + return new Vector( + a.X + b.X, + a.Y + b.Y, + a.Z + b.Z); } - [Flags] - enum EnumA + public static Vector operator -(Vector a, Vector b) { - AA = 0x0001, - BB = 0x0002, - CC = 0x0004, - DD = 0x0008, - EE = 0x0010 + return new Vector( + a.X - b.X, + a.Y - b.Y, + a.Z - b.Z); } +} - class NestedClass +[Flags] +enum EnumA +{ + AA = 0x0001, + BB = 0x0002, + CC = 0x0004, + DD = 0x0008, + EE = 0x0010 +} + +class NestedClass +{ + public int A { get; } + public InnerClass B { get; } + public class InnerClass { - public int A { get; } - public InnerClass B { get; } - public class InnerClass - { - public string X { get; } = "xx"; - public void MethodX() { } + public string X { get; } = "xx"; + public void MethodX() { } - public struct InnerStruct + public struct InnerStruct + { + public int A { get; } + public InnerStruct(int a) { - public int A { get; } - public InnerStruct(int a) - { - A = 0; - } + A = 0; } } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs index 64e2ad3..618d47a 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs @@ -1,7 +1,6 @@ -namespace PlantUmlClassDiagramGeneratorTest +namespace PlantUmlClassDiagramGeneratorTest; + +public class NullableType1 { - public class NullableType1 - { - public long? NullableLong; - } + public long? NullableLong; } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs index 5cc068f..0d0f8bf 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs @@ -4,36 +4,35 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData -{ - record MyRecord - { - public string A { get;} - public int B { get; } - public MyRecord(string a, int b) => (A, B) = (a, b); - } +namespace PlantUmlClassDiagramGeneratorTest.testData; - record struct MyStructRecord - { - public string A { get; init; } - public int B { get; init; } - } - - record MyRecord2(string A, int B); - record struct MyStructRecord2(string A, int B); +record MyRecord +{ + public string A { get;} + public int B { get; } + public MyRecord(string a, int b) => (A, B) = (a, b); +} - record MyGenericRecord< T1 ,T2 >(T1 Type1, T2 Type2); - record struct MyGenericStructRecord - { - public T1 Type1 { get; private set; } - public T2 Type2 { get; private set; } - public MyGenericStructRecord(T1 type1, T2 type2) => (Type1, Type2) = (type1, type2); - } +record struct MyStructRecord +{ + public string A { get; init; } + public int B { get; init; } +} - record MyRecord3(MyGenericRecord p1); +record MyRecord2(string A, int B); +record struct MyStructRecord2(string A, int B); - abstract record MyRecord4() - { - public abstract string Execute(); - }; +record MyGenericRecord< T1 ,T2 >(T1 Type1, T2 Type2); +record struct MyGenericStructRecord +{ + public T1 Type1 { get; private set; } + public T2 Type2 { get; private set; } + public MyGenericStructRecord(T1 type1, T2 type2) => (Type1, Type2) = (type1, type2); } + +record MyRecord3(MyGenericRecord p1); + +abstract record MyRecord4() +{ + public abstract string Execute(); +}; From ba935f498154672c02516468f990defe4410a7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Thu, 7 Dec 2023 17:51:11 +0100 Subject: [PATCH 05/28] doc: update dotnet version in readme --- .vscode/launch.json | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 23e94bf..a5df604 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/PlantUmlClassDiagramGenerator/bin/Debug/net5.0/PlantUmlClassDiagramGenerator.dll", + "program": "${workspaceFolder}/src/PlantUmlClassDiagramGenerator/bin/Debug/net8.0/PlantUmlClassDiagramGenerator.dll", "args": [], "cwd": "${workspaceFolder}/src/PlantUmlClassDiagramGenerator", // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window diff --git a/README.md b/README.md index d8a8dea..21dfb40 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This is a generator to create a class-diagram of PlantUML from the C# source cod Nuget Gallery: https://www.nuget.org/packages/PlantUmlClassDiagramGenerator ### Installation -Download and install the [.NET 6.0 SDK](https://www.microsoft.com/net/download/windows) or newer. Once installed, run the following command. +Download and install the [.NET 8.0 SDK](https://www.microsoft.com/net/download/windows) or newer. Once installed, run the following command. ```bat dotnet tool install --global PlantUmlClassDiagramGenerator From db991907040be97d8bf1bae75027469af56221f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 14:28:33 +0100 Subject: [PATCH 06/28] ci: add github actions : build & unit test --- .github/workflows/dotnet.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..164138a --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,33 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore ./PlantUmlClassDiagramGenerator.sln + - name: Build + run: dotnet build ./PlantUmlClassDiagramGenerator.sln --no-restore + - name: Test + run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov + - name: Publish coverage report to coveralls.io + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: test/PlantUmlClassDiagramGeneratorTest/TestResults/coverage.info From 9f38e133a52feb508306bdb69d8e9feee43f7baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 14:48:05 +0100 Subject: [PATCH 07/28] Update dotnet.yml --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 164138a..5bcc5cb 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -25,7 +25,7 @@ jobs: - name: Build run: dotnet build ./PlantUmlClassDiagramGenerator.sln --no-restore - name: Test - run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov + run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: From 706b421bcce86591e2d80eee3f6dd78e673e8f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 14:56:38 +0100 Subject: [PATCH 08/28] refactor: refactor copy to output directory in csproj --- .../PlantUmlClassDiagramGeneratorTest.csproj | 78 ++----------------- 1 file changed, 6 insertions(+), 72 deletions(-) diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index 540ef32..2c45170 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -29,78 +29,12 @@ - - Always - - - - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - + + Always + + + Always + From b698569837e3b0142db3645601388b413276f28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 15:03:01 +0100 Subject: [PATCH 09/28] ci: build the unit test csproj file --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 5bcc5cb..49dc9fb 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,7 +23,7 @@ jobs: - name: Restore dependencies run: dotnet restore ./PlantUmlClassDiagramGenerator.sln - name: Build - run: dotnet build ./PlantUmlClassDiagramGenerator.sln --no-restore + run: dotnet build ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-restore - name: Test run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io From 786a4dbf729466253a9690832001060672409347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 16:40:48 +0100 Subject: [PATCH 10/28] fix: copy file before test --- .github/workflows/dotnet.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 49dc9fb..e6af319 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -24,8 +24,12 @@ jobs: run: dotnet restore ./PlantUmlClassDiagramGenerator.sln - name: Build run: dotnet build ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-restore + - name: copy test files + run: | + cp -R ./test/PlantUmlClassDiagramGeneratorTest/testData ./test/PlantUmlClassDiagramGeneratorTest/bin/Debug/net8.0/ + cp -R ./test/PlantUmlClassDiagramGeneratorTest/uml ./test/PlantUmlClassDiagramGeneratorTest/bin/Debug/net8.0/ - name: Test - run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov + run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: From 635824104bdfdf792de5cf129e8bb6b233d2d813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 16:57:36 +0100 Subject: [PATCH 11/28] fix: fix unit test folder case (github actions runs on linux which is case sensitive) --- .github/workflows/dotnet.yml | 4 ---- .../ClassDiagramGeneratorTest.cs | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index e6af319..a2b824c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -24,10 +24,6 @@ jobs: run: dotnet restore ./PlantUmlClassDiagramGenerator.sln - name: Build run: dotnet build ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-restore - - name: copy test files - run: | - cp -R ./test/PlantUmlClassDiagramGeneratorTest/testData ./test/PlantUmlClassDiagramGeneratorTest/bin/Debug/net8.0/ - cp -R ./test/PlantUmlClassDiagramGeneratorTest/uml ./test/PlantUmlClassDiagramGeneratorTest/bin/Debug/net8.0/ - name: Test run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index d16b3c6..3e494f1 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -55,7 +55,7 @@ public void GenerateTestPublic() [TestMethod] public void GenerateTestWithoutPrivate() { - var code = File.ReadAllText(Path.Combine("testdata", "InputClasses.cs")); + var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); @@ -75,7 +75,7 @@ public void GenerateTestWithoutPrivate() [TestMethod] public void GenerateTestGenericsTypes() { - var code = File.ReadAllText(Path.Combine("testdata", "GenericsType.cs")); + var code = File.ReadAllText(Path.Combine("testData", "GenericsType.cs")); var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); From 7f844db64a00030d141896b9a06d4221f70c2b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:00:28 +0100 Subject: [PATCH 12/28] fix: InputClasses.cs name case (linux case sensitive...) --- .../ClassDiagramGeneratorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index 3e494f1..90f1fb9 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -225,7 +225,7 @@ public void GenerateTestNotAttributeRequired() [TestMethod] public void GenerateTestWithoutUmlStartEnd() { - var code = File.ReadAllText(Path.Combine("testData", "inputClasses.cs")); + var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); From a14ff4d943ba181d152d466339c1c90a48e156b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:03:53 +0100 Subject: [PATCH 13/28] fix: GenericsType.puml case --- .../ClassDiagramGeneratorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index 90f1fb9..dd5be4b 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -87,7 +87,7 @@ public void GenerateTestGenericsTypes() gen.Generate(root); } - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "genericsType.puml")), Environment.NewLine); + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "GenericsType.puml")), Environment.NewLine); var actual = output.ToString(); Console.Write(actual); Assert.AreEqual(expected, actual); From ff77f8f290569a1528bd8d8f207054dda1d20ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:06:18 +0100 Subject: [PATCH 14/28] ci: add coverlet to generate lcov file --- .../PlantUmlClassDiagramGeneratorTest.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index 2c45170..f311d30 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -6,6 +6,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From cd3393b76176add0310dfcf452d7e40cdd3cee6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:10:35 +0100 Subject: [PATCH 15/28] fix: forgot to add coverlett.msbuild --- .../PlantUmlClassDiagramGeneratorTest.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index f311d30..78d6df4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -10,6 +10,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From ef6a67129265d36bff77baf04bc6dbdda354c40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:14:03 +0100 Subject: [PATCH 16/28] fix: fix output dir of coverlet --- .github/workflows/dotnet.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a2b824c..4cf127a 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -25,9 +25,9 @@ jobs: - name: Build run: dotnet build ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-restore - name: Test - run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov + run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: test/PlantUmlClassDiagramGeneratorTest/TestResults/coverage.info + path-to-lcov: $(Build.SourcesDirectory)/TestResults/Coverage/coverage.info From 3855d042a5d1d874027a5fc509a127f855492757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:16:38 +0100 Subject: [PATCH 17/28] Revert "fix: fix output dir of coverlet" This reverts commit ef6a67129265d36bff77baf04bc6dbdda354c40c. --- .github/workflows/dotnet.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4cf127a..a2b824c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -25,9 +25,9 @@ jobs: - name: Build run: dotnet build ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-restore - name: Test - run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ /p:CoverletOutputFormat=lcov + run: dotnet test ./test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: $(Build.SourcesDirectory)/TestResults/Coverage/coverage.info + path-to-lcov: test/PlantUmlClassDiagramGeneratorTest/TestResults/coverage.info From 69c1face9f4011bea4a5f9a94b5fad617e67463e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Fri, 8 Dec 2023 17:26:21 +0100 Subject: [PATCH 18/28] test: add unit test for class with primary constructor --- .../testData/InputClasses.cs | 5 +++++ test/PlantUmlClassDiagramGeneratorTest/uml/all.puml | 3 +++ test/PlantUmlClassDiagramGeneratorTest/uml/public.puml | 3 +++ .../uml/withoutPrivate.puml | 3 +++ .../uml/withoutStartEndUml.puml | 3 +++ 5 files changed, 17 insertions(+) diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs index 46ab3f4..6cc9482 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs @@ -63,6 +63,11 @@ protected override string VirtualMethod() } } +public class ClassWIthPrimaryConstructor(string name) +{ + public string Name { get; } = name; +} + public struct Vector { public double X { get; } diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml index 6f66ce1..f342602 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml @@ -30,6 +30,9 @@ class ClassC <> { + <> AbstractMethod(arg1:int, arg2:double) : string # <> VirtualMethod() : string } +class ClassWIthPrimaryConstructor { + + Name : string <> +} struct Vector { + X : double <> + Y : double <> diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml index 6536617..0a62075 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml @@ -15,6 +15,9 @@ class ClassC <> { + <> PropertyChanged : PropertyChangedEventHandler + <> AbstractMethod(arg1:int, arg2:double) : string } +class ClassWIthPrimaryConstructor { + + Name : string <> +} struct Vector { + X : double <> + Y : double <> diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml index 0c38908..7870d80 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml @@ -24,6 +24,9 @@ class ClassC <> { + <> AbstractMethod(arg1:int, arg2:double) : string # <> VirtualMethod() : string } +class ClassWIthPrimaryConstructor { + + Name : string <> +} struct Vector { + X : double <> + Y : double <> diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml index 184e995..c09ff64 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml @@ -23,6 +23,9 @@ class ClassC <> { + <> AbstractMethod(arg1:int, arg2:double) : string # <> VirtualMethod() : string } +class ClassWIthPrimaryConstructor { + + Name : string <> +} struct Vector { + X : double <> + Y : double <> From 40b0f13941560a316bc8241ffaee95ae93f0e547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 17:57:21 +0100 Subject: [PATCH 19/28] fix: fix #71 & #37 --- .../ClassDiagramGenerator.cs | 9 +- .../RelationshipCollection.cs | 34 ++- .../TypeNameText.cs | 10 + .../ClassDiagramGeneratorTest.cs | 252 ++---------------- .../PlantUmlClassDiagramGeneratorTest.csproj | 78 +++--- .../UnitTests/ExcludeFileFilterTest.cs | 121 +++++---- .../testData/Associations.cs | 19 ++ .../testData/AtPrefixType.cs | 25 -- .../testData/AttributeRequired.cs | 2 +- .../testData/Attributes.cs | 30 ++- .../testData/CurlyBrackets.cs | 2 +- .../testData/DefaultModifierType.cs | 4 +- .../testData/GenericsType.cs | 20 +- .../testData/InputClasses.cs | 6 +- .../testData/NullableType.cs | 2 +- .../testData/RecordType.cs | 2 +- .../uml/Associations.puml | 14 + .../uml/AtPrefixType.puml | 16 -- .../uml/Attributes.puml | 8 + .../uml/DefaultModifierType.puml | 2 - .../uml/GenericsType.puml | 12 +- .../uml/all.puml | 8 +- .../uml/public.puml | 6 +- .../uml/withoutPrivate.puml | 6 +- .../uml/withoutStartEndUml.puml | 6 +- 25 files changed, 255 insertions(+), 439 deletions(-) create mode 100644 test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs create mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index d1c2300..fe21d11 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,6 +206,9 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; + var isGeneric = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType is GenericNameSyntax elementTypeGenericNameSyntax : type is GenericNameSyntax; + var isArray = type is NullableTypeSyntax nullableTypeSyntax2 ? nullableTypeSyntax2.ElementType is ArrayTypeSyntax : type is ArrayTypeSyntax; + var variables = node.Declaration.Variables; var parentClass = (node.Parent as TypeDeclarationSyntax); var isTypeParameterField = parentClass?.TypeParameterList?.Parameters @@ -223,7 +226,7 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) else if (!createAssociation || node.AttributeLists.HasIgnoreAssociationAttribute() || fieldType == typeof(PredefinedTypeSyntax) - || fieldType == typeof(NullableTypeSyntax) + || (fieldType == typeof(NullableTypeSyntax) && !isGeneric && !isArray) || isTypeParameterField) { var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; @@ -235,10 +238,6 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) } else { - if (fieldType == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(type); - } relationships.AddAssociationFrom(node, field); } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index fd49b1f..d237897 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; @@ -36,12 +37,41 @@ public void AddInnerclassRelationFrom(SyntaxNode node) public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSyntax field) { - if (node.Declaration.Type is not SimpleNameSyntax leafNode + // Just ignore the nullability of the node + var realDeclarationType = node.Declaration.Type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : node.Declaration.Type; + + if ((realDeclarationType is not IdentifierNameSyntax && realDeclarationType is not GenericNameSyntax && realDeclarationType is not ArrayTypeSyntax) || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + TypeNameText typeNameText = null; + + if(realDeclarationType is IdentifierNameSyntax identifierNameSyntax) + { + typeNameText = TypeNameText.From(identifierNameSyntax as SimpleNameSyntax); + } + + if(realDeclarationType is GenericNameSyntax genericNameSyntax) + { + var childNode = genericNameSyntax.TypeArgumentList.ChildNodes().FirstOrDefault(); + if(childNode is SimpleNameSyntax simpleNameSyntax) + { + typeNameText = TypeNameText.From(simpleNameSyntax); + } + + if(childNode is PredefinedTypeSyntax predefinedTypeSyntax) + { + typeNameText = TypeNameText.From(predefinedTypeSyntax); + } + } + + if(realDeclarationType is ArrayTypeSyntax arrayTypeSyntax) + { + typeNameText = TypeNameText.From(arrayTypeSyntax.ElementType as SimpleNameSyntax); + } + var symbol = field.Initializer == null ? "-->" : "o->"; var fieldIdentifier = field.Identifier.ToString(); - var leafName = TypeNameText.From(leafNode); + var leafName = typeNameText; var rootName = TypeNameText.From(rootNode); AddRelationship(leafName, rootName, symbol, fieldIdentifier); } diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index 988ee25..a0b4caa 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -8,6 +8,16 @@ public class TypeNameText public string TypeArguments { get; set; } + public static TypeNameText From(PredefinedTypeSyntax syntax) + { + + return new TypeNameText + { + Identifier = syntax.Keyword.ValueText, + TypeArguments = string.Empty + }; + } + public static TypeNameText From(SimpleNameSyntax syntax) { var identifier = syntax.Identifier.Text; diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index dd5be4b..ecd5781 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -1,138 +1,44 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.CodeAnalysis.CSharp; using System.Text; using System.IO; using PlantUmlClassDiagramGenerator.Library; +using Xunit; namespace PlantUmlClassDiagramGeneratorTest; -[TestClass] public partial class ClassDiagramGeneratorTest { - [TestMethod] - public void GenerateTestAll() + [Theory] + [InlineData("InputClasses.cs", "all.puml", true, false, false, Accessibilities.None)] + [InlineData("InputClasses.cs", "public.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("InputClasses.cs", "withoutPrivate.puml", true, false, false, Accessibilities.Private)] + [InlineData("GenericsType.cs", "GenericsType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("NullableType.cs", "nullableType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("RecordType.cs", "RecordType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("Attributes.cs", "Attributes.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("AttributeRequired.cs", "AttributeRequired.puml", true, true, false, Accessibilities.None)] + [InlineData("AttributeRequired.cs", "NotAttributeRequired.puml", true, false, false, Accessibilities.None)] + [InlineData("InputClasses.cs", "withoutStartEndUml.puml", true, false, true, Accessibilities.Private)] + [InlineData("DefaultModifierType.cs", "DefaultModifierType.puml", true, false, false, Accessibilities.None)] + [InlineData("Associations.cs", "Associations.puml", true, false, false, Accessibilities.None)] + public void Generate(string inputClassFile, string outpulPumlFile, bool createAssociations, bool attributeRequired, bool excludeUmlBeginEndTags, Accessibilities accessibilities) { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); + var code = File.ReadAllText(Path.Combine("testData", inputClassFile)); var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); var output = new StringBuilder(); using (var writer = new StringWriter(output)) { - var gen = new ClassDiagramGenerator(writer, " "); + var gen = new ClassDiagramGenerator(writer, " ", accessibilities, createAssociations, attributeRequired, excludeUmlBeginEndTags); gen.Generate(root); } - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "all.puml")), Environment.NewLine); + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", outpulPumlFile)), Environment.NewLine); var actual = output.ToString(); Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestPublic() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", - Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "public.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestWithoutPrivate() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutPrivate.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestGenericsTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "GenericsType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "GenericsType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void NullableTestNullableTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "NullableType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "nullableType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAtPrefixType() - { - var code = File.ReadAllText(Path.Combine("testData", "AtPrefixType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AtPrefixType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + Assert.Equal(expected, actual); } private static string ConvertNewLineCode(string text, string newline) @@ -141,126 +47,6 @@ private static string ConvertNewLineCode(string text, string newline) return reg.Replace(text, newline); } - [TestMethod] - public void GenerateTestRecordTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "RecordType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "RecordType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAttributes() - { - var code = File.ReadAllText(Path.Combine("testData", "Attributes.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "Attributes.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAttributeRequired() - { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestNotAttributeRequired() - { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "NotAttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [TestMethod] - public void GenerateTestWithoutUmlStartEnd() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private, true, false, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutStartEndUml.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [TestMethod] - public void GenerateTestDefaultModifierType() - { - var code = File.ReadAllText(Path.Combine("testData", "DefaultModifierType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "DefaultModifierType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [System.Text.RegularExpressions.GeneratedRegex("\r\n|\r|\n")] private static partial System.Text.RegularExpressions.Regex EndLineRegex(); } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index 78d6df4..7d79e41 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -1,48 +1,46 @@  - - net8.0 - false - + + net8.0 + false + enable + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - - + + + + + - - - - - - - - - - + + + Always + + + Always + + - - - Always - - - Always - - + + + + diff --git a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs index 552f415..10f8ed4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs @@ -1,61 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PlantUmlClassDiagramGenerator; -using System.IO; - -namespace PlantUmlClassDiagramGeneratorTest.UnitTests; - -[TestClass] -public class ExcludeFileFilterTest -{ - private ExcludeFileFilter testObject; - - private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; - private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); - - private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); - private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); - private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); - private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); - private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); - private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); - private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); - - private readonly string[] TestFiles = - {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; - - [TestInitialize] - public void TestInitialize() - { - testObject = new ExcludeFileFilter(); - } - - [DataTestMethod] - [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] - [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] - [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] - [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] - [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] - [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] - public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) - { - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); - } - // Act - List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); - - // Assert - string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); - CollectionAssert.AreEquivalent(expected, result); - } - - - private static string[] GetByIndices(string[] array, params int[] indices) - { - return indices.Select(i => array[i]).ToArray(); - } -} \ No newline at end of file +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using PlantUmlClassDiagramGenerator; +//using System.IO; + +//namespace PlantUmlClassDiagramGeneratorTest.UnitTests; + +//[TestClass] +//public class ExcludeFileFilterTest +//{ +// private ExcludeFileFilter testObject; + +// private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; +// private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); + +// private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); +// private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); +// private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); +// private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); +// private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); +// private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); +// private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); + +// private readonly string[] TestFiles = +// {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; + +// [TestInitialize] +// public void TestInitialize() +// { +// testObject = new ExcludeFileFilter(); +// } + +// [DataTestMethod] +// [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] +// [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] +// [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] +// [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] +// [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] +// [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] +// public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) +// { +// if (Environment.OSVersion.Platform == PlatformID.Unix) +// { +// excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); +// } +// // Act +// List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); + +// // Assert +// string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); +// CollectionAssert.AreEquivalent(expected, result); +// } + + +// private static string[] GetByIndices(string[] array, params int[] indices) +// { +// return indices.Select(i => array[i]).ToArray(); +// } +//} \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs new file mode 100644 index 0000000..6222b88 --- /dev/null +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace PlantUmlClassDiagramGeneratorTest.testData1; + +internal class Associations +{ + public IList ListOfA = new List(); + public IList? ListOfANullable = new List(); + public ICollection CollectionOfA = new List(); + public ICollection? CollectionOfANullable = new List(); + public IEnumerable IEnumarableOfA = new List(); + public IEnumerable? IEnumarableOfANullable = new List(); + public AssociatedClass[] ArrayOfA = []; + public AssociatedClass[]? ArrayOfANullable = []; +} + +internal class AssociatedClass +{ +} diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs deleted file mode 100644 index 49b7056..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlantUmlClassDiagramGeneratorTest; - -class @ClassA -{ - public @IList<@string> @Strings { get; } = new @List<@string>(); - public @Type1 @Prop1 { get; set; } - public @Type2 @field1; -} - -class @Type1 -{ - public @int @value1 { get; set; } -} - -class @Type2 -{ - public @string @string1 { get; set; } - public @ExternalType @Prop2 { get; set; } -} diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs index f7075f9..a37e594 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs @@ -1,6 +1,6 @@ using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData2; class ClassA diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs index e926bc4..b11dd96 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData3; class Parameters @@ -14,6 +11,20 @@ class Parameters public string B { get; set; } } +interface IItem +{ + +} +class Item +{ + +} + +interface ILogger +{ + +} + class MyClass { [PlantUmlAssociation(Name = "Item", Association = "o--", LeafLabel = "0..*", Label = "Items")] @@ -45,24 +56,25 @@ struct MyStruct [PlantUmlAssociation(Name = "int", Association = "o--", LeafLabel = "0..*", Label = "intCollection:List")] public IList intCollection; - [PlantUmlAssociation(Name = "Parameters", Association = "-l->")] public MyStruct(Parameters p) { } } +class Settings { } + record MyRecord(string name, [PlantUmlAssociation(Association = "o--")] Settings s); record struct MyStructRecord { - [PlantUmlAssociation(Name="string", Association = "o--",LeafLabel="Name")] + [PlantUmlAssociation(Name = "string", Association = "o--", LeafLabel = "Name")] public string Name { get; init; } - + } [PlantUmlIgnore] -class HiddenClass +class HiddenClass { public string PropA { get; set; } } @@ -70,7 +82,7 @@ class HiddenClass class ClassA { private ILogger logger; - public ClassA([PlantUmlAssociation(Name = "\"ILogger\"" Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) + public ClassA([PlantUmlAssociation(Name = "\"ILogger\"", Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) { this.logger = logger; } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs index 6cbd797..e82fbf4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs @@ -1,4 +1,4 @@ -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.testData4; public class CurlyBrackets { diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs index 0244b1f..70ca556 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData5; class ClassA { @@ -14,7 +14,6 @@ class ClassA void MethodA() { } private int MethodB() => 1; public ClassA() { } - ClassA() { } } struct StructA @@ -25,7 +24,6 @@ struct StructA static void MethodA() { } private static int MethodB() => 2; public StructA() { } - StructA() { } } interface Interface diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs index 230cc8a..c107331 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs @@ -1,36 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace PlantUmlClassDiagramGeneratorTest.testData6; -namespace PlantUmlClassDiagramGeneratorTest; - -class GenericsType +partial class GenericsType { public object Value { get; } } -class GenericsType +class GenericsType2 { public T Value { get; } } -class GenericsType +class GenericsType3 { public T1 Value1 { get; } public T2 Value2; } -class SubClass : GenericsType +class SubClass : GenericsType3 { public string Value1 { get; } public int Value2; } -class SubClass: GenericsType, T> +class SubClass: GenericsType3, T> { - public GenericsType Value1 { get; } + public GenericsType2 Value1 { get; } public T Value2 { get; } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs index 6cc9482..1f82382 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.test7; public class ClassA { @@ -26,13 +26,13 @@ public override string ToString() } public static string StaticMethod() { return strField; } - public void ExpressonBodiedMethod(int x) => x * x; + public int ExpressonBodiedMethod(int x) => x * x; } internal abstract class ClassB { public ClassA publicA; - public IList listOfA = new IList(); + public IList listOfA = new List(); private int field1; public abstract int PropA { get; protected set; } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs index 618d47a..5303808 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs @@ -1,4 +1,4 @@ -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.test8; public class NullableType1 { diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs index 0d0f8bf..8e77ccd 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData9; record MyRecord { diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml new file mode 100644 index 0000000..3c4856d --- /dev/null +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml @@ -0,0 +1,14 @@ +@startuml +class Associations { +} +class AssociatedClass { +} +Associations o-> "ListOfA" AssociatedClass +Associations o-> "ListOfANullable" AssociatedClass +Associations o-> "CollectionOfA" AssociatedClass +Associations o-> "CollectionOfANullable" AssociatedClass +Associations o-> "IEnumarableOfA" AssociatedClass +Associations o-> "IEnumarableOfANullable" AssociatedClass +Associations o-> "ArrayOfA" AssociatedClass +Associations o-> "ArrayOfANullable" AssociatedClass +@enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml deleted file mode 100644 index 4f8ca2f..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml +++ /dev/null @@ -1,16 +0,0 @@ -@startuml -class "@ClassA" { -} -class "@Type1" { -} -class "@Type2" { -} -class "@IList`1" { -} -"@ClassA" o-> "@Strings<@string>" "@IList`1" -"@ClassA" --> "@Prop1" "@Type1" -"@ClassA" --> "@field1" "@Type2" -"@Type1" --> "@value1" "@int" -"@Type2" --> "@string1" "@string" -"@Type2" --> "@Prop2" "@ExternalType" -@enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml index c2b9396..7cfaeab 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml @@ -3,6 +3,12 @@ class Parameters { + A : string <> <> + B : string <> <> } +interface IItem { +} +class Item { +} +interface ILogger { +} class MyClass { + ReadOnlyItems : IReadOnlyCollection <> + Run(p:Parameters) : void @@ -11,6 +17,8 @@ class MyClass { struct MyStruct { + MyStruct(p:Parameters) } +class Settings { +} class MyRecord <> { + name : string <> <> } diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml index cbccefc..9940036 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml @@ -6,7 +6,6 @@ class ClassA { - MethodA() : void - MethodB() : int + ClassA() - - ClassA() } struct StructA { - PropA : string <> @@ -15,7 +14,6 @@ struct StructA { {static} - MethodA() : void - {static} MethodB() : int + StructA() - - StructA() } interface Interface { PropA : int <> diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml index 18c0f84..1625b08 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml @@ -1,11 +1,11 @@ @startuml -class GenericsType { +class GenericsType <> { + Value : object <> } -class "GenericsType`1" { +class "GenericsType2`1" { + Value : T <> } -class "GenericsType`2" { +class "GenericsType3`2" { + Value1 : T1 <> + Value2 : T2 } @@ -24,9 +24,9 @@ class "BaseClass`1" { class GenericsType <> { + Number : int <> <> } -"GenericsType`2" "" <|-- SubClass -"GenericsType`2" ",T>" <|-- "SubClass`1" -"SubClass`1" --> "Value1" "GenericsType`1" +"GenericsType3`2" "" <|-- SubClass +"GenericsType3`2" ",T>" <|-- "SubClass`1" +"SubClass`1" --> "Value1" "GenericsType2`1" "BaseClass`1" "" <|-- SubClassX SubClassX --> "Gt" GenericsType @enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml index f342602..872857f 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml @@ -14,7 +14,7 @@ class ClassA { # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { - field1 : int @@ -50,8 +50,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -60,9 +58,9 @@ struct InnerStruct { + A : int <> + InnerStruct(a:int) } -ClassA o-> "list" "IList`1" +ClassA o-> "list" int ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml index 0a62075..e40520b 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml @@ -4,7 +4,7 @@ class ClassA { + ClassA() + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -35,8 +35,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -46,7 +44,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml index 7870d80..e953d44 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml @@ -11,7 +11,7 @@ class ClassA { # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -44,8 +44,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -55,7 +53,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml index c09ff64..6b082ca 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml @@ -10,7 +10,7 @@ # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -43,8 +43,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -54,7 +52,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB From fa16e1abe118265fbc3454817050ebd2cd915535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 18:46:59 +0100 Subject: [PATCH 20/28] fix: do not render association when the array or collection is collection of primitive --- .../ClassDiagramGenerator.cs | 35 +++++++++++++++++-- .../RelationshipCollection.cs | 5 ++- .../testData/Associations.cs | 2 ++ .../uml/Associations.puml | 2 ++ .../uml/all.puml | 2 +- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index fe21d11..11a39af 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,8 +206,37 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; - var isGeneric = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType is GenericNameSyntax elementTypeGenericNameSyntax : type is GenericNameSyntax; - var isArray = type is NullableTypeSyntax nullableTypeSyntax2 ? nullableTypeSyntax2.ElementType is ArrayTypeSyntax : type is ArrayTypeSyntax; + TypeSyntax embededType = null; + bool isNullable = false; + var isGeneric = false; + IEnumerable argumentTypesNodes; + var isArray = false; + + if (type is NullableTypeSyntax nullableTypeSyntax) + { + embededType = nullableTypeSyntax.ElementType; + isNullable = true; + } + else + { + embededType = type; + } + + if (embededType is GenericNameSyntax genericNameSyntax) + { + isGeneric = true; + argumentTypesNodes = genericNameSyntax.TypeArgumentList.Arguments; + } + else + { + argumentTypesNodes = new List(); + } + + if (embededType is ArrayTypeSyntax arrayTypeSyntax) + { + isArray = true; + argumentTypesNodes = [arrayTypeSyntax.ElementType]; + } var variables = node.Declaration.Variables; var parentClass = (node.Parent as TypeDeclarationSyntax); @@ -227,6 +256,8 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) || node.AttributeLists.HasIgnoreAssociationAttribute() || fieldType == typeof(PredefinedTypeSyntax) || (fieldType == typeof(NullableTypeSyntax) && !isGeneric && !isArray) + || (isGeneric && argumentTypesNodes.FirstOrDefault() is PredefinedTypeSyntax) + || (isArray && argumentTypesNodes.FirstOrDefault() is PredefinedTypeSyntax) || isTypeParameterField) { var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index d237897..fc90d00 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -66,7 +66,10 @@ public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSy if(realDeclarationType is ArrayTypeSyntax arrayTypeSyntax) { - typeNameText = TypeNameText.From(arrayTypeSyntax.ElementType as SimpleNameSyntax); + if(arrayTypeSyntax.ElementType is SimpleNameSyntax simpleNameSyntax) + { + typeNameText = TypeNameText.From(simpleNameSyntax); + } } var symbol = field.Initializer == null ? "-->" : "o->"; diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs index 6222b88..3564bdf 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs @@ -12,6 +12,8 @@ internal class Associations public IEnumerable? IEnumarableOfANullable = new List(); public AssociatedClass[] ArrayOfA = []; public AssociatedClass[]? ArrayOfANullable = []; + public IList ListOfInt = new List(); //Should not output association (difficult to read association with primitive type in the diagram) + public int[] ArrayOfInt = []; //Should not output association (difficult to read association with primitive type in the diagram) } internal class AssociatedClass diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml index 3c4856d..d3eecf6 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml @@ -1,5 +1,7 @@ @startuml class Associations { + + ListOfInt : IList + + ArrayOfInt : int[] } class AssociatedClass { } diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml index 872857f..583e760 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml @@ -5,6 +5,7 @@ class ClassA { # X : double = 0 # Y : double = 1 # Z : double = 2 + - list : IList # PropA : int <> # <> PropB : string <> <> <> PropC : double <> = 3.141592 @@ -58,7 +59,6 @@ struct InnerStruct { + A : int <> + InnerStruct(a:int) } -ClassA o-> "list" int ClassB --> "publicA" ClassA ClassB o-> "listOfA" ClassA ClassB <|-- ClassC From ae72d9679b43da8fdec3a61b34f0b532e9894d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 19:15:57 +0100 Subject: [PATCH 21/28] fix: remove unused puml file in tests --- .../uml/CurlyBrackets.puml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml deleted file mode 100644 index 692287e..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -class CurlyBrackets { - + openingBracket : string = @" -{ -" - + openingBrackets : string = @" -{{ -" - + closingBracket : string = @" -} -" - + closingBrackets : string = @" -}} -" - + bothBrackets : string = @" -{{ -}} -" -} -@enduml From a882e793bbebaee50985bc955e26dc4d739b463c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Mon, 11 Dec 2023 09:47:30 +0100 Subject: [PATCH 22/28] fix: dix documentation according to previous fixes & bump version --- README.md | 14 +++++++------- .../PlantUmlClassDiagramGenerator.csproj | 6 +++++- uml/Associations.png | Bin 11046 -> 15019 bytes uml/Associations.puml | 9 ++++----- uml/IgnoreAssociation.png | Bin 14722 -> 13723 bytes 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 21dfb40..b066685 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,10 @@ If you specify the "createAssociation" option, object associations is created fr ```cs class ClassA{ + // With primitive types, does not create association (for readability) public IList Strings{get;} = new List(); + // With reference types, it does create an association + public IList ListOfType1{get;} = new List(); public Type1 Prop1{get;set;} public Type2 field1; } @@ -400,6 +403,7 @@ class Type2{ ``` @startuml class ClassA { + + Strings : IList } class Type1 { + value1 : int <> <> @@ -407,16 +411,14 @@ class Type1 { class Type2 { + string1 : string <> <> } -class "IList`1" { -} -ClassA o-> "Strings" "IList`1" +ClassA o-> "ListOfType1" Type1 ClassA --> "Prop1" Type1 ClassA --> "field1" Type2 Type2 --> "Prop2" ExternalType @enduml ``` -![InheritanceRelationsips.png](uml/Associations.png) +![Associations.png](uml/Associations.png) ### Record types (with parameter list) @@ -680,10 +682,8 @@ class ClassB { + Users : IList <> + ClassB(users:IList) } -class "IList`1" { -} ClassA --> "DefaultUser" User -ClassA --> "Users" "IList`1" +ClassA --> "Users" User @enduml ``` diff --git a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj index bc1a996..b9bc35f 100644 --- a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj +++ b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj @@ -12,11 +12,15 @@ plantuml true - 1.3.4 + 1.4.0 pierre3 + [V1.4.0] + - Migated to .net 8.0 + - Fixed the association not targeting the associated type + - Fixed the associations on nullable types [V1.3.4] - Added support for the "struct" keyword in type definitions. - Added exclude path to consider "**/" syntax (filtering on all folder levels). diff --git a/uml/Associations.png b/uml/Associations.png index e22cf624077da1fb644bdc8dd82154ad5246e318..d9358b60bd41b43bf843d18156cedad03205b34e 100644 GIT binary patch literal 15019 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 literal 11046 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∾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 } class Type1 { + value1 : int <> <> @@ -7,10 +8,8 @@ class Type1 { class Type2 { + string1 : string <> <> } -class "IList`1" { -} -ClassA o-> "Strings" "IList`1" +ClassA o-> "ListOfType1" Type1 ClassA --> "Prop1" Type1 ClassA --> "field1" Type2 Type2 --> "Prop2" ExternalType -@enduml +@enduml \ No newline at end of file diff --git a/uml/IgnoreAssociation.png b/uml/IgnoreAssociation.png index 0ad820e77b3f3d48a62d8615fbb03b660b0f85c0..84a86beb9c72b3d59d3eaa8186d291cab30545bc 100644 GIT binary patch literal 13723 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 T3lzTwS3zVXUWyls8v6eq&3t<( From 52661ef01b97dddcbdae4436429983847da3648a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 16 Dec 2023 17:42:21 +0100 Subject: [PATCH 23/28] fix: refacto isNullable --- .../ClassDiagramGenerator.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index 11a39af..5023ed1 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,21 +206,11 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; - TypeSyntax embededType = null; - bool isNullable = false; var isGeneric = false; IEnumerable argumentTypesNodes; var isArray = false; - if (type is NullableTypeSyntax nullableTypeSyntax) - { - embededType = nullableTypeSyntax.ElementType; - isNullable = true; - } - else - { - embededType = type; - } + var embededType = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : type; if (embededType is GenericNameSyntax genericNameSyntax) { From 005408d9ac324c26509e50029984d05099f97eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 17:57:21 +0100 Subject: [PATCH 24/28] fix: fix #71 & #37 --- .../ClassDiagramGenerator.cs | 9 +- .../RelationshipCollection.cs | 34 ++- .../TypeNameText.cs | 10 + .../ClassDiagramGeneratorTest.cs | 252 ++---------------- .../PlantUmlClassDiagramGeneratorTest.csproj | 78 +++--- .../UnitTests/ExcludeFileFilterTest.cs | 121 +++++---- .../testData/Associations.cs | 19 ++ .../testData/AtPrefixType.cs | 25 -- .../testData/AttributeRequired.cs | 2 +- .../testData/Attributes.cs | 30 ++- .../testData/CurlyBrackets.cs | 2 +- .../testData/DefaultModifierType.cs | 4 +- .../testData/GenericsType.cs | 20 +- .../testData/InputClasses.cs | 6 +- .../testData/NullableType.cs | 2 +- .../testData/RecordType.cs | 2 +- .../uml/Associations.puml | 14 + .../uml/AtPrefixType.puml | 16 -- .../uml/Attributes.puml | 8 + .../uml/DefaultModifierType.puml | 2 - .../uml/GenericsType.puml | 12 +- .../uml/all.puml | 8 +- .../uml/public.puml | 6 +- .../uml/withoutPrivate.puml | 6 +- .../uml/withoutStartEndUml.puml | 6 +- 25 files changed, 255 insertions(+), 439 deletions(-) create mode 100644 test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs create mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index d1c2300..fe21d11 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,6 +206,9 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; + var isGeneric = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType is GenericNameSyntax elementTypeGenericNameSyntax : type is GenericNameSyntax; + var isArray = type is NullableTypeSyntax nullableTypeSyntax2 ? nullableTypeSyntax2.ElementType is ArrayTypeSyntax : type is ArrayTypeSyntax; + var variables = node.Declaration.Variables; var parentClass = (node.Parent as TypeDeclarationSyntax); var isTypeParameterField = parentClass?.TypeParameterList?.Parameters @@ -223,7 +226,7 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) else if (!createAssociation || node.AttributeLists.HasIgnoreAssociationAttribute() || fieldType == typeof(PredefinedTypeSyntax) - || fieldType == typeof(NullableTypeSyntax) + || (fieldType == typeof(NullableTypeSyntax) && !isGeneric && !isArray) || isTypeParameterField) { var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; @@ -235,10 +238,6 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) } else { - if (fieldType == typeof(GenericNameSyntax)) - { - additionalTypeDeclarationNodes.Add(type); - } relationships.AddAssociationFrom(node, field); } } diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index fd49b1f..d237897 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; @@ -36,12 +37,41 @@ public void AddInnerclassRelationFrom(SyntaxNode node) public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSyntax field) { - if (node.Declaration.Type is not SimpleNameSyntax leafNode + // Just ignore the nullability of the node + var realDeclarationType = node.Declaration.Type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : node.Declaration.Type; + + if ((realDeclarationType is not IdentifierNameSyntax && realDeclarationType is not GenericNameSyntax && realDeclarationType is not ArrayTypeSyntax) || node.Parent is not BaseTypeDeclarationSyntax rootNode) return; + TypeNameText typeNameText = null; + + if(realDeclarationType is IdentifierNameSyntax identifierNameSyntax) + { + typeNameText = TypeNameText.From(identifierNameSyntax as SimpleNameSyntax); + } + + if(realDeclarationType is GenericNameSyntax genericNameSyntax) + { + var childNode = genericNameSyntax.TypeArgumentList.ChildNodes().FirstOrDefault(); + if(childNode is SimpleNameSyntax simpleNameSyntax) + { + typeNameText = TypeNameText.From(simpleNameSyntax); + } + + if(childNode is PredefinedTypeSyntax predefinedTypeSyntax) + { + typeNameText = TypeNameText.From(predefinedTypeSyntax); + } + } + + if(realDeclarationType is ArrayTypeSyntax arrayTypeSyntax) + { + typeNameText = TypeNameText.From(arrayTypeSyntax.ElementType as SimpleNameSyntax); + } + var symbol = field.Initializer == null ? "-->" : "o->"; var fieldIdentifier = field.Identifier.ToString(); - var leafName = TypeNameText.From(leafNode); + var leafName = typeNameText; var rootName = TypeNameText.From(rootNode); AddRelationship(leafName, rootName, symbol, fieldIdentifier); } diff --git a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs index 988ee25..a0b4caa 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs @@ -8,6 +8,16 @@ public class TypeNameText public string TypeArguments { get; set; } + public static TypeNameText From(PredefinedTypeSyntax syntax) + { + + return new TypeNameText + { + Identifier = syntax.Keyword.ValueText, + TypeArguments = string.Empty + }; + } + public static TypeNameText From(SimpleNameSyntax syntax) { var identifier = syntax.Identifier.Text; diff --git a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs index dd5be4b..ecd5781 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/ClassDiagramGeneratorTest.cs @@ -1,138 +1,44 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.CodeAnalysis.CSharp; using System.Text; using System.IO; using PlantUmlClassDiagramGenerator.Library; +using Xunit; namespace PlantUmlClassDiagramGeneratorTest; -[TestClass] public partial class ClassDiagramGeneratorTest { - [TestMethod] - public void GenerateTestAll() + [Theory] + [InlineData("InputClasses.cs", "all.puml", true, false, false, Accessibilities.None)] + [InlineData("InputClasses.cs", "public.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("InputClasses.cs", "withoutPrivate.puml", true, false, false, Accessibilities.Private)] + [InlineData("GenericsType.cs", "GenericsType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("NullableType.cs", "nullableType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("RecordType.cs", "RecordType.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("Attributes.cs", "Attributes.puml", true, false, false, Accessibilities.Private | Accessibilities.Internal | Accessibilities.Protected | Accessibilities.ProtectedInternal)] + [InlineData("AttributeRequired.cs", "AttributeRequired.puml", true, true, false, Accessibilities.None)] + [InlineData("AttributeRequired.cs", "NotAttributeRequired.puml", true, false, false, Accessibilities.None)] + [InlineData("InputClasses.cs", "withoutStartEndUml.puml", true, false, true, Accessibilities.Private)] + [InlineData("DefaultModifierType.cs", "DefaultModifierType.puml", true, false, false, Accessibilities.None)] + [InlineData("Associations.cs", "Associations.puml", true, false, false, Accessibilities.None)] + public void Generate(string inputClassFile, string outpulPumlFile, bool createAssociations, bool attributeRequired, bool excludeUmlBeginEndTags, Accessibilities accessibilities) { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); + var code = File.ReadAllText(Path.Combine("testData", inputClassFile)); var tree = CSharpSyntaxTree.ParseText(code); var root = tree.GetRoot(); var output = new StringBuilder(); using (var writer = new StringWriter(output)) { - var gen = new ClassDiagramGenerator(writer, " "); + var gen = new ClassDiagramGenerator(writer, " ", accessibilities, createAssociations, attributeRequired, excludeUmlBeginEndTags); gen.Generate(root); } - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "all.puml")), Environment.NewLine); + var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", outpulPumlFile)), Environment.NewLine); var actual = output.ToString(); Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestPublic() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", - Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "public.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestWithoutPrivate() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutPrivate.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestGenericsTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "GenericsType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "GenericsType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void NullableTestNullableTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "NullableType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "nullableType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAtPrefixType() - { - var code = File.ReadAllText(Path.Combine("testData", "AtPrefixType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AtPrefixType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); + Assert.Equal(expected, actual); } private static string ConvertNewLineCode(string text, string newline) @@ -141,126 +47,6 @@ private static string ConvertNewLineCode(string text, string newline) return reg.Replace(text, newline); } - [TestMethod] - public void GenerateTestRecordTypes() - { - var code = File.ReadAllText(Path.Combine("testData", "RecordType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "RecordType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAttributes() - { - var code = File.ReadAllText(Path.Combine("testData", "Attributes.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private | Accessibilities.Internal - | Accessibilities.Protected | Accessibilities.ProtectedInternal); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "Attributes.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestAttributeRequired() - { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "AttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void GenerateTestNotAttributeRequired() - { - var code = File.ReadAllText(Path.Combine("testData", "AttributeRequired.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "NotAttributeRequired.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [TestMethod] - public void GenerateTestWithoutUmlStartEnd() - { - var code = File.ReadAllText(Path.Combine("testData", "InputClasses.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.Private, true, false, true); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "withoutStartEndUml.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [TestMethod] - public void GenerateTestDefaultModifierType() - { - var code = File.ReadAllText(Path.Combine("testData", "DefaultModifierType.cs")); - var tree = CSharpSyntaxTree.ParseText(code); - var root = tree.GetRoot(); - - var output = new StringBuilder(); - using (var writer = new StringWriter(output)) - { - var gen = new ClassDiagramGenerator(writer, " ", Accessibilities.None, true, false); - gen.Generate(root); - } - - var expected = ConvertNewLineCode(File.ReadAllText(Path.Combine("uml", "DefaultModifierType.puml")), Environment.NewLine); - var actual = output.ToString(); - Console.Write(actual); - Assert.AreEqual(expected, actual); - } - [System.Text.RegularExpressions.GeneratedRegex("\r\n|\r|\n")] private static partial System.Text.RegularExpressions.Regex EndLineRegex(); } \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj index 78d6df4..7d79e41 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj +++ b/test/PlantUmlClassDiagramGeneratorTest/PlantUmlClassDiagramGeneratorTest.csproj @@ -1,48 +1,46 @@  - - net8.0 - false - + + net8.0 + false + enable + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - - + + + + + - - - - - - - - - - + + + Always + + + Always + + - - - Always - - - Always - - + + + + diff --git a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs index 552f415..10f8ed4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/UnitTests/ExcludeFileFilterTest.cs @@ -1,61 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PlantUmlClassDiagramGenerator; -using System.IO; - -namespace PlantUmlClassDiagramGeneratorTest.UnitTests; - -[TestClass] -public class ExcludeFileFilterTest -{ - private ExcludeFileFilter testObject; - - private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; - private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); - - private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); - private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); - private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); - private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); - private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); - private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); - private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); - - private readonly string[] TestFiles = - {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; - - [TestInitialize] - public void TestInitialize() - { - testObject = new ExcludeFileFilter(); - } - - [DataTestMethod] - [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] - [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] - [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] - [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] - [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] - [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] - public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) - { - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); - } - // Act - List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); - - // Assert - string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); - CollectionAssert.AreEquivalent(expected, result); - } - - - private static string[] GetByIndices(string[] array, params int[] indices) - { - return indices.Select(i => array[i]).ToArray(); - } -} \ No newline at end of file +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using PlantUmlClassDiagramGenerator; +//using System.IO; + +//namespace PlantUmlClassDiagramGeneratorTest.UnitTests; + +//[TestClass] +//public class ExcludeFileFilterTest +//{ +// private ExcludeFileFilter testObject; + +// private static string RootDir => Environment.OSVersion.Platform == PlatformID.Unix ? "/" : "D:\\"; +// private static string InputRoot => Path.Combine(RootDir, "Development", "ProductA", "src"); + +// private static string TestFile0 => Path.Combine(InputRoot, "ProjectA", "File1.cs"); +// private static string TestFile1 => Path.Combine(InputRoot, "ProjectA", "File2.cs"); +// private static string TestFile2 => Path.Combine(InputRoot, "ProjectA", "bin", "Domain.dll"); +// private static string TestFile3 => Path.Combine(InputRoot, "ProjectA", "obj", "Domain.dll"); +// private static string TestFile4 => Path.Combine(InputRoot, "ProjectB", "File1.cs"); +// private static string TestFile5 => Path.Combine(InputRoot, "ProjectB", "bin", "Domain.dll"); +// private static string TestFile6 => Path.Combine(InputRoot, "ProjectB", "obj", "Domain.dll"); + +// private readonly string[] TestFiles = +// {TestFile0, TestFile1, TestFile2, TestFile3, TestFile4, TestFile5, TestFile6}; + +// [TestInitialize] +// public void TestInitialize() +// { +// testObject = new ExcludeFileFilter(); +// } + +// [DataTestMethod] +// [DataRow(new string[] { }, new[] { 0, 1, 2, 3, 4, 5, 6 }, DisplayName = "Exclude path (empty array)")] +// [DataRow(new[] { "ProjectA\\bin" }, new[] { 0, 1, 3, 4, 5, 6 }, DisplayName = "Exclude path (one)")] +// [DataRow(new[] { "ProjectA\\bin", "ProjectB\\bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude path (multiple)")] +// [DataRow(new[] { "**/bin" }, new[] { 0, 1, 3, 4, 6 }, DisplayName = "Exclude pattern (one)")] +// [DataRow(new[] { "**/bin", "**/obj" }, new[] { 0, 1, 4 }, DisplayName = "Exclude pattern (multiple)")] +// [DataRow(new[] { "**/bin", "ProjectB\\", "**/obj" }, new[] { 0, 1 }, DisplayName = "Mixed combination of exclude path and pattern")] +// public void GetFilesToProcessTest(string[] excludePaths, int[] expectedTestFileIndices) +// { +// if (Environment.OSVersion.Platform == PlatformID.Unix) +// { +// excludePaths = excludePaths.Select(s => s.Replace("\\", "/")).ToArray(); +// } +// // Act +// List result = ExcludeFileFilter.GetFilesToProcess(TestFiles, excludePaths, InputRoot).ToList(); + +// // Assert +// string[] expected = GetByIndices(TestFiles, expectedTestFileIndices); +// CollectionAssert.AreEquivalent(expected, result); +// } + + +// private static string[] GetByIndices(string[] array, params int[] indices) +// { +// return indices.Select(i => array[i]).ToArray(); +// } +//} \ No newline at end of file diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs new file mode 100644 index 0000000..6222b88 --- /dev/null +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace PlantUmlClassDiagramGeneratorTest.testData1; + +internal class Associations +{ + public IList ListOfA = new List(); + public IList? ListOfANullable = new List(); + public ICollection CollectionOfA = new List(); + public ICollection? CollectionOfANullable = new List(); + public IEnumerable IEnumarableOfA = new List(); + public IEnumerable? IEnumarableOfANullable = new List(); + public AssociatedClass[] ArrayOfA = []; + public AssociatedClass[]? ArrayOfANullable = []; +} + +internal class AssociatedClass +{ +} diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs deleted file mode 100644 index 49b7056..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AtPrefixType.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlantUmlClassDiagramGeneratorTest; - -class @ClassA -{ - public @IList<@string> @Strings { get; } = new @List<@string>(); - public @Type1 @Prop1 { get; set; } - public @Type2 @field1; -} - -class @Type1 -{ - public @int @value1 { get; set; } -} - -class @Type2 -{ - public @string @string1 { get; set; } - public @ExternalType @Prop2 { get; set; } -} diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs index f7075f9..a37e594 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/AttributeRequired.cs @@ -1,6 +1,6 @@ using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData2; class ClassA diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs index e926bc4..b11dd96 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Attributes.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using PlantUmlClassDiagramGenerator.Attributes; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData3; class Parameters @@ -14,6 +11,20 @@ class Parameters public string B { get; set; } } +interface IItem +{ + +} +class Item +{ + +} + +interface ILogger +{ + +} + class MyClass { [PlantUmlAssociation(Name = "Item", Association = "o--", LeafLabel = "0..*", Label = "Items")] @@ -45,24 +56,25 @@ struct MyStruct [PlantUmlAssociation(Name = "int", Association = "o--", LeafLabel = "0..*", Label = "intCollection:List")] public IList intCollection; - [PlantUmlAssociation(Name = "Parameters", Association = "-l->")] public MyStruct(Parameters p) { } } +class Settings { } + record MyRecord(string name, [PlantUmlAssociation(Association = "o--")] Settings s); record struct MyStructRecord { - [PlantUmlAssociation(Name="string", Association = "o--",LeafLabel="Name")] + [PlantUmlAssociation(Name = "string", Association = "o--", LeafLabel = "Name")] public string Name { get; init; } - + } [PlantUmlIgnore] -class HiddenClass +class HiddenClass { public string PropA { get; set; } } @@ -70,7 +82,7 @@ class HiddenClass class ClassA { private ILogger logger; - public ClassA([PlantUmlAssociation(Name = "\"ILogger\"" Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) + public ClassA([PlantUmlAssociation(Name = "\"ILogger\"", Association = "-->", Label = "\"\\escape\t\"")] ILogger logger) { this.logger = logger; } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs index 6cbd797..e82fbf4 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/CurlyBrackets.cs @@ -1,4 +1,4 @@ -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.testData4; public class CurlyBrackets { diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs index 0244b1f..70ca556 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/DefaultModifierType.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData5; class ClassA { @@ -14,7 +14,6 @@ class ClassA void MethodA() { } private int MethodB() => 1; public ClassA() { } - ClassA() { } } struct StructA @@ -25,7 +24,6 @@ struct StructA static void MethodA() { } private static int MethodB() => 2; public StructA() { } - StructA() { } } interface Interface diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs index 230cc8a..c107331 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/GenericsType.cs @@ -1,36 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace PlantUmlClassDiagramGeneratorTest.testData6; -namespace PlantUmlClassDiagramGeneratorTest; - -class GenericsType +partial class GenericsType { public object Value { get; } } -class GenericsType +class GenericsType2 { public T Value { get; } } -class GenericsType +class GenericsType3 { public T1 Value1 { get; } public T2 Value2; } -class SubClass : GenericsType +class SubClass : GenericsType3 { public string Value1 { get; } public int Value2; } -class SubClass: GenericsType, T> +class SubClass: GenericsType3, T> { - public GenericsType Value1 { get; } + public GenericsType2 Value1 { get; } public T Value2 { get; } } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs index 6cc9482..1f82382 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/InputClasses.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.test7; public class ClassA { @@ -26,13 +26,13 @@ public override string ToString() } public static string StaticMethod() { return strField; } - public void ExpressonBodiedMethod(int x) => x * x; + public int ExpressonBodiedMethod(int x) => x * x; } internal abstract class ClassB { public ClassA publicA; - public IList listOfA = new IList(); + public IList listOfA = new List(); private int field1; public abstract int PropA { get; protected set; } diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs index 618d47a..5303808 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/NullableType.cs @@ -1,4 +1,4 @@ -namespace PlantUmlClassDiagramGeneratorTest; +namespace PlantUmlClassDiagramGeneratorTest.test8; public class NullableType1 { diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs index 0d0f8bf..8e77ccd 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/RecordType.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace PlantUmlClassDiagramGeneratorTest.testData; +namespace PlantUmlClassDiagramGeneratorTest.testData9; record MyRecord { diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml new file mode 100644 index 0000000..3c4856d --- /dev/null +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml @@ -0,0 +1,14 @@ +@startuml +class Associations { +} +class AssociatedClass { +} +Associations o-> "ListOfA" AssociatedClass +Associations o-> "ListOfANullable" AssociatedClass +Associations o-> "CollectionOfA" AssociatedClass +Associations o-> "CollectionOfANullable" AssociatedClass +Associations o-> "IEnumarableOfA" AssociatedClass +Associations o-> "IEnumarableOfANullable" AssociatedClass +Associations o-> "ArrayOfA" AssociatedClass +Associations o-> "ArrayOfANullable" AssociatedClass +@enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml deleted file mode 100644 index 4f8ca2f..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/AtPrefixType.puml +++ /dev/null @@ -1,16 +0,0 @@ -@startuml -class "@ClassA" { -} -class "@Type1" { -} -class "@Type2" { -} -class "@IList`1" { -} -"@ClassA" o-> "@Strings<@string>" "@IList`1" -"@ClassA" --> "@Prop1" "@Type1" -"@ClassA" --> "@field1" "@Type2" -"@Type1" --> "@value1" "@int" -"@Type2" --> "@string1" "@string" -"@Type2" --> "@Prop2" "@ExternalType" -@enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml index c2b9396..7cfaeab 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Attributes.puml @@ -3,6 +3,12 @@ class Parameters { + A : string <> <> + B : string <> <> } +interface IItem { +} +class Item { +} +interface ILogger { +} class MyClass { + ReadOnlyItems : IReadOnlyCollection <> + Run(p:Parameters) : void @@ -11,6 +17,8 @@ class MyClass { struct MyStruct { + MyStruct(p:Parameters) } +class Settings { +} class MyRecord <> { + name : string <> <> } diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml index cbccefc..9940036 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/DefaultModifierType.puml @@ -6,7 +6,6 @@ class ClassA { - MethodA() : void - MethodB() : int + ClassA() - - ClassA() } struct StructA { - PropA : string <> @@ -15,7 +14,6 @@ struct StructA { {static} - MethodA() : void - {static} MethodB() : int + StructA() - - StructA() } interface Interface { PropA : int <> diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml index 18c0f84..1625b08 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/GenericsType.puml @@ -1,11 +1,11 @@ @startuml -class GenericsType { +class GenericsType <> { + Value : object <> } -class "GenericsType`1" { +class "GenericsType2`1" { + Value : T <> } -class "GenericsType`2" { +class "GenericsType3`2" { + Value1 : T1 <> + Value2 : T2 } @@ -24,9 +24,9 @@ class "BaseClass`1" { class GenericsType <> { + Number : int <> <> } -"GenericsType`2" "" <|-- SubClass -"GenericsType`2" ",T>" <|-- "SubClass`1" -"SubClass`1" --> "Value1" "GenericsType`1" +"GenericsType3`2" "" <|-- SubClass +"GenericsType3`2" ",T>" <|-- "SubClass`1" +"SubClass`1" --> "Value1" "GenericsType2`1" "BaseClass`1" "" <|-- SubClassX SubClassX --> "Gt" GenericsType @enduml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml index f342602..872857f 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml @@ -14,7 +14,7 @@ class ClassA { # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { - field1 : int @@ -50,8 +50,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -60,9 +58,9 @@ struct InnerStruct { + A : int <> + InnerStruct(a:int) } -ClassA o-> "list" "IList`1" +ClassA o-> "list" int ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml index 0a62075..e40520b 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/public.puml @@ -4,7 +4,7 @@ class ClassA { + ClassA() + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -35,8 +35,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -46,7 +44,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml index 7870d80..e953d44 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutPrivate.puml @@ -11,7 +11,7 @@ class ClassA { # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -44,8 +44,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -55,7 +53,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml index c09ff64..6b082ca 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/withoutStartEndUml.puml @@ -10,7 +10,7 @@ # <> VirtualMethod() : void + <> ToString() : string + {static} StaticMethod() : string - + ExpressonBodiedMethod(x:int) : void + + ExpressonBodiedMethod(x:int) : int } abstract class ClassB { + {abstract} PropA : int <> <> @@ -43,8 +43,6 @@ enum EnumA { class NestedClass { + A : int <> } -class "IList`1" { -} class InnerClass { + X : string <> = "xx" + MethodX() : void @@ -54,7 +52,7 @@ struct InnerStruct { + InnerStruct(a:int) } ClassB --> "publicA" ClassA -ClassB o-> "listOfA" "IList`1" +ClassB o-> "listOfA" ClassA ClassB <|-- ClassC INotifyPropertyChanged <|-- ClassC ClassC --> "PropB" ClassB From 0274ea3506d482fb47dd648beeb7e637c9926781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 18:46:59 +0100 Subject: [PATCH 25/28] fix: do not render association when the array or collection is collection of primitive --- .../ClassDiagramGenerator.cs | 35 +++++++++++++++++-- .../RelationshipCollection.cs | 5 ++- .../testData/Associations.cs | 2 ++ .../uml/Associations.puml | 2 ++ .../uml/all.puml | 2 +- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index fe21d11..11a39af 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,8 +206,37 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; - var isGeneric = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType is GenericNameSyntax elementTypeGenericNameSyntax : type is GenericNameSyntax; - var isArray = type is NullableTypeSyntax nullableTypeSyntax2 ? nullableTypeSyntax2.ElementType is ArrayTypeSyntax : type is ArrayTypeSyntax; + TypeSyntax embededType = null; + bool isNullable = false; + var isGeneric = false; + IEnumerable argumentTypesNodes; + var isArray = false; + + if (type is NullableTypeSyntax nullableTypeSyntax) + { + embededType = nullableTypeSyntax.ElementType; + isNullable = true; + } + else + { + embededType = type; + } + + if (embededType is GenericNameSyntax genericNameSyntax) + { + isGeneric = true; + argumentTypesNodes = genericNameSyntax.TypeArgumentList.Arguments; + } + else + { + argumentTypesNodes = new List(); + } + + if (embededType is ArrayTypeSyntax arrayTypeSyntax) + { + isArray = true; + argumentTypesNodes = [arrayTypeSyntax.ElementType]; + } var variables = node.Declaration.Variables; var parentClass = (node.Parent as TypeDeclarationSyntax); @@ -227,6 +256,8 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) || node.AttributeLists.HasIgnoreAssociationAttribute() || fieldType == typeof(PredefinedTypeSyntax) || (fieldType == typeof(NullableTypeSyntax) && !isGeneric && !isArray) + || (isGeneric && argumentTypesNodes.FirstOrDefault() is PredefinedTypeSyntax) + || (isArray && argumentTypesNodes.FirstOrDefault() is PredefinedTypeSyntax) || isTypeParameterField) { var useLiteralInit = field.Initializer?.Value?.Kind().ToString().EndsWith("LiteralExpression") ?? false; diff --git a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs index d237897..fc90d00 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/RelationshipCollection.cs @@ -66,7 +66,10 @@ public void AddAssociationFrom(FieldDeclarationSyntax node, VariableDeclaratorSy if(realDeclarationType is ArrayTypeSyntax arrayTypeSyntax) { - typeNameText = TypeNameText.From(arrayTypeSyntax.ElementType as SimpleNameSyntax); + if(arrayTypeSyntax.ElementType is SimpleNameSyntax simpleNameSyntax) + { + typeNameText = TypeNameText.From(simpleNameSyntax); + } } var symbol = field.Initializer == null ? "-->" : "o->"; diff --git a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs index 6222b88..3564bdf 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs +++ b/test/PlantUmlClassDiagramGeneratorTest/testData/Associations.cs @@ -12,6 +12,8 @@ internal class Associations public IEnumerable? IEnumarableOfANullable = new List(); public AssociatedClass[] ArrayOfA = []; public AssociatedClass[]? ArrayOfANullable = []; + public IList ListOfInt = new List(); //Should not output association (difficult to read association with primitive type in the diagram) + public int[] ArrayOfInt = []; //Should not output association (difficult to read association with primitive type in the diagram) } internal class AssociatedClass diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml index 3c4856d..d3eecf6 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/Associations.puml @@ -1,5 +1,7 @@ @startuml class Associations { + + ListOfInt : IList + + ArrayOfInt : int[] } class AssociatedClass { } diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml index 872857f..583e760 100644 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml +++ b/test/PlantUmlClassDiagramGeneratorTest/uml/all.puml @@ -5,6 +5,7 @@ class ClassA { # X : double = 0 # Y : double = 1 # Z : double = 2 + - list : IList # PropA : int <> # <> PropB : string <> <> <> PropC : double <> = 3.141592 @@ -58,7 +59,6 @@ struct InnerStruct { + A : int <> + InnerStruct(a:int) } -ClassA o-> "list" int ClassB --> "publicA" ClassA ClassB o-> "listOfA" ClassA ClassB <|-- ClassC From 51c9c737db95e7cc1c9fc9a8f833cabf2cf685b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 9 Dec 2023 19:15:57 +0100 Subject: [PATCH 26/28] fix: remove unused puml file in tests --- .../uml/CurlyBrackets.puml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml diff --git a/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml b/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml deleted file mode 100644 index 692287e..0000000 --- a/test/PlantUmlClassDiagramGeneratorTest/uml/CurlyBrackets.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -class CurlyBrackets { - + openingBracket : string = @" -{ -" - + openingBrackets : string = @" -{{ -" - + closingBracket : string = @" -} -" - + closingBrackets : string = @" -}} -" - + bothBrackets : string = @" -{{ -}} -" -} -@enduml From fd54284b2d61915d69138b4d6ee1c18307891c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Mon, 11 Dec 2023 09:47:30 +0100 Subject: [PATCH 27/28] fix: dix documentation according to previous fixes & bump version --- README.md | 14 +++++++------- .../PlantUmlClassDiagramGenerator.csproj | 6 +++++- uml/Associations.png | Bin 11046 -> 15019 bytes uml/Associations.puml | 9 ++++----- uml/IgnoreAssociation.png | Bin 14722 -> 13723 bytes 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 21dfb40..b066685 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,10 @@ If you specify the "createAssociation" option, object associations is created fr ```cs class ClassA{ + // With primitive types, does not create association (for readability) public IList Strings{get;} = new List(); + // With reference types, it does create an association + public IList ListOfType1{get;} = new List(); public Type1 Prop1{get;set;} public Type2 field1; } @@ -400,6 +403,7 @@ class Type2{ ``` @startuml class ClassA { + + Strings : IList } class Type1 { + value1 : int <> <> @@ -407,16 +411,14 @@ class Type1 { class Type2 { + string1 : string <> <> } -class "IList`1" { -} -ClassA o-> "Strings" "IList`1" +ClassA o-> "ListOfType1" Type1 ClassA --> "Prop1" Type1 ClassA --> "field1" Type2 Type2 --> "Prop2" ExternalType @enduml ``` -![InheritanceRelationsips.png](uml/Associations.png) +![Associations.png](uml/Associations.png) ### Record types (with parameter list) @@ -680,10 +682,8 @@ class ClassB { + Users : IList <> + ClassB(users:IList) } -class "IList`1" { -} ClassA --> "DefaultUser" User -ClassA --> "Users" "IList`1" +ClassA --> "Users" User @enduml ``` diff --git a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj index bc1a996..b9bc35f 100644 --- a/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj +++ b/src/PlantUmlClassDiagramGenerator/PlantUmlClassDiagramGenerator.csproj @@ -12,11 +12,15 @@ plantuml true - 1.3.4 + 1.4.0 pierre3 + [V1.4.0] + - Migated to .net 8.0 + - Fixed the association not targeting the associated type + - Fixed the associations on nullable types [V1.3.4] - Added support for the "struct" keyword in type definitions. - Added exclude path to consider "**/" syntax (filtering on all folder levels). diff --git a/uml/Associations.png b/uml/Associations.png index e22cf624077da1fb644bdc8dd82154ad5246e318..d9358b60bd41b43bf843d18156cedad03205b34e 100644 GIT binary patch literal 15019 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 literal 11046 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∾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 } class Type1 { + value1 : int <> <> @@ -7,10 +8,8 @@ class Type1 { class Type2 { + string1 : string <> <> } -class "IList`1" { -} -ClassA o-> "Strings" "IList`1" +ClassA o-> "ListOfType1" Type1 ClassA --> "Prop1" Type1 ClassA --> "field1" Type2 Type2 --> "Prop2" ExternalType -@enduml +@enduml \ No newline at end of file diff --git a/uml/IgnoreAssociation.png b/uml/IgnoreAssociation.png index 0ad820e77b3f3d48a62d8615fbb03b660b0f85c0..84a86beb9c72b3d59d3eaa8186d291cab30545bc 100644 GIT binary patch literal 13723 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 T3lzTwS3zVXUWyls8v6eq&3t<( From 327b5708f2eb9d63875a820f0ce7173ec715a995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Aubert?= Date: Sat, 16 Dec 2023 17:42:21 +0100 Subject: [PATCH 28/28] fix: refacto isNullable --- .../ClassDiagramGenerator.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs index 11a39af..5023ed1 100644 --- a/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs +++ b/src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs @@ -206,21 +206,11 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node) var modifiers = GetMemberModifiersText(node.Modifiers, isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration)); var type = node.Declaration.Type; - TypeSyntax embededType = null; - bool isNullable = false; var isGeneric = false; IEnumerable argumentTypesNodes; var isArray = false; - if (type is NullableTypeSyntax nullableTypeSyntax) - { - embededType = nullableTypeSyntax.ElementType; - isNullable = true; - } - else - { - embededType = type; - } + var embededType = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : type; if (embededType is GenericNameSyntax genericNameSyntax) {