Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/assotiation with nullable types #87

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ad61b72
feat: upgrade projects to .net8.0
PendingChanges Dec 7, 2023
ef1b78c
feat: upgrade nuget packages
PendingChanges Dec 7, 2023
5873658
refactor: roslyn code smells fixes
PendingChanges Dec 7, 2023
3a747c7
refactor: convert namespaces to file scoped namespaces
PendingChanges Dec 7, 2023
ba935f4
doc: update dotnet version in readme
PendingChanges Dec 7, 2023
c054329
refactor: upgrade to .net 8.0
PendingChanges Dec 7, 2023
db99190
ci: add github actions : build & unit test
PendingChanges Dec 8, 2023
9f38e13
Update dotnet.yml
PendingChanges Dec 8, 2023
706b421
refactor: refactor copy to output directory in csproj
PendingChanges Dec 8, 2023
b698569
ci: build the unit test csproj file
PendingChanges Dec 8, 2023
786a4db
fix: copy file before test
PendingChanges Dec 8, 2023
6358241
fix: fix unit test folder case (github actions runs on linux which is…
PendingChanges Dec 8, 2023
7f844db
fix: InputClasses.cs name case (linux case sensitive...)
PendingChanges Dec 8, 2023
a14ff4d
fix: GenericsType.puml case
PendingChanges Dec 8, 2023
ff77f8f
ci: add coverlet to generate lcov file
PendingChanges Dec 8, 2023
cd3393b
fix: forgot to add coverlett.msbuild
PendingChanges Dec 8, 2023
ef6a671
fix: fix output dir of coverlet
PendingChanges Dec 8, 2023
3855d04
Revert "fix: fix output dir of coverlet"
PendingChanges Dec 8, 2023
69c1fac
test: add unit test for class with primary constructor
PendingChanges Dec 8, 2023
40b0f13
fix: fix #71 & #37
PendingChanges Dec 9, 2023
fa16e1a
fix: do not render association when the array or collection is collec…
PendingChanges Dec 9, 2023
ae72d96
fix: remove unused puml file in tests
PendingChanges Dec 9, 2023
a882e79
fix: dix documentation according to previous fixes & bump version
PendingChanges Dec 11, 2023
435e431
Merge branch 'pierre3:master' into master
PendingChanges Dec 16, 2023
52661ef
fix: refacto isNullable
PendingChanges Dec 16, 2023
005408d
fix: fix #71 & #37
PendingChanges Dec 9, 2023
0274ea3
fix: do not render association when the array or collection is collec…
PendingChanges Dec 9, 2023
51c9c73
fix: remove unused puml file in tests
PendingChanges Dec 9, 2023
fd54284
fix: dix documentation according to previous fixes & bump version
PendingChanges Dec 11, 2023
327b570
fix: refacto isNullable
PendingChanges Dec 16, 2023
3d5490b
Merge branch 'features/assotiation-with-nullable-types' of https://gi…
PendingChanges Dec 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> Strings{get;} = new List<string>();
// With reference types, it does create an association
public IList<Type1> ListOfType1{get;} = new List<Type1>();
public Type1 Prop1{get;set;}
public Type2 field1;
}
Expand All @@ -400,23 +403,22 @@ class Type2{
```
@startuml
class ClassA {
+ Strings : IList<string>
}
class Type1 {
+ value1 : int <<get>> <<set>>
}
class Type2 {
+ string1 : string <<get>> <<set>>
}
class "IList`1"<T> {
}
ClassA o-> "Strings<string>" "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)
Expand Down Expand Up @@ -680,10 +682,8 @@ class ClassB {
+ Users : IList<User> <<get>>
+ ClassB(users:IList<User>)
}
class "IList`1"<T> {
}
ClassA --> "DefaultUser" User
ClassA --> "Users<User>" "IList`1"
ClassA --> "Users" User
@enduml
```

Expand Down
30 changes: 25 additions & 5 deletions src/PlantUmlClassDiagramGenerator.Library/ClassDiagramGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,28 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
var modifiers = GetMemberModifiersText(node.Modifiers,
isInterfaceMember: node.Parent.IsKind(SyntaxKind.InterfaceDeclaration));
var type = node.Declaration.Type;
var isGeneric = false;
IEnumerable<SyntaxNode> argumentTypesNodes;
var isArray = false;

var embededType = type is NullableTypeSyntax nullableTypeSyntax ? nullableTypeSyntax.ElementType : type;

if (embededType is GenericNameSyntax genericNameSyntax)
{
isGeneric = true;
argumentTypesNodes = genericNameSyntax.TypeArgumentList.Arguments;
}
else
{
argumentTypesNodes = new List<TypeSyntax>();
}

if (embededType is ArrayTypeSyntax arrayTypeSyntax)
{
isArray = true;
argumentTypesNodes = [arrayTypeSyntax.ElementType];
}

var variables = node.Declaration.Variables;
var parentClass = (node.Parent as TypeDeclarationSyntax);
var isTypeParameterField = parentClass?.TypeParameterList?.Parameters
Expand All @@ -223,7 +245,9 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
else if (!createAssociation
|| node.AttributeLists.HasIgnoreAssociationAttribute()
|| fieldType == typeof(PredefinedTypeSyntax)
|| fieldType == typeof(NullableTypeSyntax)
|| (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;
Expand All @@ -235,10 +259,6 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
}
else
{
if (fieldType == typeof(GenericNameSyntax))
{
additionalTypeDeclarationNodes.Add(type);
}
relationships.AddAssociationFrom(node, field);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -36,12 +37,44 @@ 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)
{
if(arrayTypeSyntax.ElementType is SimpleNameSyntax simpleNameSyntax)
{
typeNameText = TypeNameText.From(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);
}
Expand Down
10 changes: 10 additions & 0 deletions src/PlantUmlClassDiagramGenerator.Library/TypeNameText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
<RepositoryType></RepositoryType>
<PackageTags>plantuml</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.3.4</Version>
<Version>1.4.0</Version>
<Authors>pierre3</Authors>
<Company />
<Product />
<PackageReleaseNotes>
[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).
Expand Down
Loading