Skip to content

Commit

Permalink
Fix: efcore non nullable navigation property check (#481)
Browse files Browse the repository at this point in the history
* suppress null checks on navigation properties

* add handler for class specific field gen

* make nav prop field nullable

* logic to set backfield for nav prop

* generate nav property load check

* remove semi colon after property
  • Loading branch information
dpvreony authored Jul 22, 2024
1 parent fca3e97 commit 8a38a99
Show file tree
Hide file tree
Showing 14 changed files with 151 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public sealed class RequestDtoClassGeneratorProcessor : BaseClassLevelCodeGenera

protected override bool GetWhetherClassShouldBeSealedClass() => true;

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments() => Array.Empty<string>();

protected override IList<string> GetUsings() => Array.Empty<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public sealed class ResponseDtoClassGeneratorProcessor : BaseClassLevelCodeGener

protected override IList<string> GetBaseConstructorArguments() => null;

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public class EntityFrameworkEntityTypeConfigurationGeneratorProcessor : BaseClas
///<inheritdoc />
protected override bool GetWhetherClassShouldBeSealedClass() => true;

///<inheritdoc />
protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclarations(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
return Array.Empty<PropertyDeclarationSyntax>();
Expand Down Expand Up @@ -110,6 +109,11 @@ protected override IList<Tuple<string, IList<string>>> GetClassAttributes(Entity
return null;
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
return null;
}

///<inheritdoc />
protected override IList<string> GetBaseConstructorArguments() => null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ namespace Dhgms.Nucleotide.Generators.Features.EntityFramework
{
public sealed class EntityFrameworkModelGeneratorProcessor : BaseClassLevelCodeGeneratorProcessor<EntityFrameworkModelEntityGenerationModel>
{
private object? test;

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Field 'EntityFrameworkModelGeneratorProcessor.test' is never assigned to, and will always have its default value null

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Field 'EntityFrameworkModelGeneratorProcessor.test' is never assigned to, and will always have its default value null

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 19 in src/Dhgms.Nucleotide.Generators/Features/EntityFramework/EntityFrameworkModelGeneratorProcessor.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Field 'EntityFrameworkModelGeneratorProcessor.test' is never assigned to, and will always have its default value null

///<inheritdoc />
protected override bool GetWhetherClassShouldBePartialClass() => true;

///<inheritdoc />
protected override bool GetWhetherClassShouldBeSealedClass() => true;

public object SomeProp { get => test ?? throw new InvalidOperationException("failed to access"); }

///<inheritdoc />
protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclarations(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
Expand Down Expand Up @@ -66,9 +70,32 @@ protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclaration

var pocoType = SyntaxFactory.ParseTypeName($"EfModels.{referencedByEntityGenerationModel.EntityType}EfModel");

var firstLetterLower = char.ToLower(referencedByEntityGenerationModel.SingularPropertyName[0]);
var fieldName = $"_{firstLetterLower}{referencedByEntityGenerationModel.SingularPropertyName.Substring(1)}";

var assignment = SyntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
SyntaxFactory.IdentifierName(fieldName),
SyntaxFactory.IdentifierName("value"));

var exceptionArgs = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal($"Uninitialized navigation property: {referencedByEntityGenerationModel.SingularPropertyName}"))) }));
var getAccessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(SyntaxFactory.ArrowExpressionClause(CoalesceExpression(SyntaxFactory.IdentifierName(fieldName), SyntaxFactory.ThrowExpression(SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName("System.InvalidOperationException"), exceptionArgs, null)))))
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));

var setAccessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithExpressionBody(SyntaxFactory.ArrowExpressionClause(assignment))
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));

var accessorList = new[]
{
getAccessor,
setAccessor
};

yield return RoslynGenerationHelpers.GetPropertyDeclarationSyntax(
pocoType,
referencedByEntityGenerationModel.SingularPropertyName,
accessorList,
inheritDocSyntaxTrivia);
}
}
Expand All @@ -79,14 +106,26 @@ protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclaration
{
var pocoType = SyntaxFactory.ParseTypeName($"global::System.Collections.Generic.ICollection<EfModels.{referencedByEntityGenerationModel.EntityType}EfModel>");

var suppress = SyntaxFactory.PostfixUnaryExpression(SyntaxKind.SuppressNullableWarningExpression,
SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression));

var initializer = SyntaxFactory.EqualsValueClause(
SyntaxFactory.Token(SyntaxKind.EqualsToken),
suppress);

yield return RoslynGenerationHelpers.GetPropertyDeclarationSyntax(
pocoType,
referencedByEntityGenerationModel.PluralPropertyName,
inheritDocSyntaxTrivia);
inheritDocSyntaxTrivia).WithInitializer(initializer).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
}
}
}

private static BinaryExpressionSyntax CoalesceExpression(ExpressionSyntax left, ExpressionSyntax right)
{
return SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, left, right);
}

///<inheritdoc />
protected override PropertyDeclarationSyntax GetPropertyDeclaration(PropertyInfoBase propertyInfo, AccessorDeclarationSyntax[] accessorList, IEnumerable<SyntaxTrivia> summary)
{
Expand Down Expand Up @@ -154,6 +193,33 @@ protected override IList<Tuple<string, IList<string>>> GetClassAttributes(Entity
return null;
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(EntityFrameworkModelEntityGenerationModel entityGenerationModel)
{
if (entityGenerationModel.ParentEntityRelationships == null)
{
return null;
}

var result = new List<FieldDeclarationSyntax>();
foreach (var referencedByEntityGenerationModel in entityGenerationModel.ParentEntityRelationships)
{
var fieldType = SyntaxFactory.ParseTypeName($"EfModels.{referencedByEntityGenerationModel.EntityType}EfModel?");

var firstLetterLower = char.ToLower(referencedByEntityGenerationModel.SingularPropertyName[0]);
var fieldName = $"_{firstLetterLower}{referencedByEntityGenerationModel.SingularPropertyName.Substring(1)}";

var declaration = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(
fieldType,
SyntaxFactory.SeparatedList(new[] { SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(fieldName)) })
))
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword));
result.Add(declaration);
}

return result.ToArray();
}

///<inheritdoc />
protected override IList<string> GetBaseConstructorArguments() => null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ protected override IList<Tuple<string, IList<string>>> GetClassAttributes(IEntit
return new List<Tuple<string, IList<string>>>();
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

/// <inheritdoc />
protected override IList<string> GetBaseConstructorArguments()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class KeyedModelClassGeneratorProcessor : BaseClassLevelCodeGeneratorProc

protected override string GetClassSuffix() => "Model";

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments() => null;

protected override IList<string> GetUsings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class UnkeyedModelClassGeneratorProcessor : BaseClassLevelCodeGeneratorPr

protected override bool GetWhetherClassShouldBeSealedClass() => false;

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments() => null;

protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclarations(IEntityGenerationModel entityGenerationModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ protected override IList<Tuple<Func<string, string>, string, Accessibility>> Get
return result;
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments()
{
var result = new List<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// DHGMS Solutions and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Generic;
using Dhgms.Nucleotide.Generators.Features.ReactiveUI.Wpf;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Dhgms.Nucleotide.Generators.Features.ReactiveUI
{
Expand All @@ -19,6 +21,11 @@ protected override string[] GetClassLevelCommentSummary(string entityName)
};
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(ReactiveWindowGenerationModel entityGenerationModel)
{
return null;
}

protected override string GetBaseClass(ReactiveWindowGenerationModel entityGenerationModel)
{
return $"global::ReactiveUI.ReactiveWindow<{entityGenerationModel.ViewModelType}>";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// DHGMS Solutions and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Dhgms.Nucleotide.Generators.Features.ReactiveUI.Wpf
{
public sealed class ReactiveMetroWindowClassGeneratorProcessor : AbstractReactiveWindowClassGeneratorProcessor
Expand All @@ -17,6 +20,11 @@ protected override string[] GetClassLevelCommentSummary(string entityName)
};
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(ReactiveWindowGenerationModel entityGenerationModel)
{
return null;
}

protected override string GetBaseClass(ReactiveWindowGenerationModel entityGenerationModel)
{
return $"global::Whipstaff.Wpf.Mahapps.ReactiveMetroWindow<{entityGenerationModel.ViewModelType}>";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// DHGMS Solutions and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Dhgms.Nucleotide.Generators.Features.ReactiveUI.Wpf
{
public sealed class ReactiveSimpleChildWindowClassGeneratorProcessor : AbstractReactiveWindowClassGeneratorProcessor
Expand All @@ -17,6 +20,11 @@ protected override string[] GetClassLevelCommentSummary(string entityName)
};
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(ReactiveWindowGenerationModel entityGenerationModel)
{
return null;
}

protected override string GetBaseClass(ReactiveWindowGenerationModel entityGenerationModel)
{
return $"global::Whipstaff.Wpf.Mahapps.ReactiveSimpleChildWindow<{entityGenerationModel.ViewModelType}>";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ protected override IEnumerable<PropertyDeclarationSyntax> GetPropertyDeclaration

protected override bool GetWhetherClassShouldBeSealedClass() => true;

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments() => null;

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ protected override IList<Tuple<Func<string, string>, string, Accessibility>> Get
return result;
}

protected override IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(IEntityGenerationModel entityGenerationModel)
{
return null;
}

protected override IList<string> GetBaseConstructorArguments()
{
var result = new List<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,19 @@ protected MemberDeclarationSyntax[] GetMembers(string className, string entityNa
var constructorArguments = GetConstructorArguments();
var baseArguments = GetBaseConstructorArguments();

var fields = GetFieldDeclarations(constructorArguments, baseArguments, entityName);
if (fields != null && fields.Length > 0)
var fields = GetConstructorFieldDeclarations(constructorArguments, baseArguments, entityName);
if (fields is { Count: > 0 })
{
result.AddRange(fields);
}

if (constructorArguments != null && constructorArguments.Count > 0)
fields = GetFieldDeclarations(entityGenerationModel);
if (fields is { Count: > 0 })
{
result.AddRange(fields);
}

if (constructorArguments is { Count: > 0 })
{
result.Add(GenerateConstructor(className, constructorArguments, entityName, baseArguments));
}
Expand All @@ -196,6 +202,8 @@ protected MemberDeclarationSyntax[] GetMembers(string className, string entityNa
return result.ToArray();
}

protected abstract IReadOnlyCollection<FieldDeclarationSyntax> GetFieldDeclarations(TGenerationModel entityGenerationModel);

protected abstract IList<string> GetBaseConstructorArguments();

protected static AttributeListSyntax GetAttributeListSyntax(IList<Tuple<string, IList<string>>> attributes)
Expand All @@ -218,7 +226,7 @@ protected static AttributeListSyntax GetAttributeListSyntax(IList<Tuple<string,
return list;
}

private MemberDeclarationSyntax[] GetFieldDeclarations(
private IReadOnlyCollection<FieldDeclarationSyntax> GetConstructorFieldDeclarations(
IList<Tuple<Func<string, string>, string, Accessibility>> constructorArguments,
IList<string> baseArguments,
string entityName)
Expand All @@ -228,8 +236,7 @@ private MemberDeclarationSyntax[] GetFieldDeclarations(
return null;
}

var result = new List<MemberDeclarationSyntax>();

var result = new List<FieldDeclarationSyntax>();
foreach (var constructorArgument in constructorArguments)
{
if (baseArguments.Any(ba => ba.Equals(constructorArgument.Item2)))
Expand All @@ -240,16 +247,15 @@ private MemberDeclarationSyntax[] GetFieldDeclarations(

var fieldType = constructorArgument.Item1(entityName);

var fieldDeclaration = SyntaxFactory.FieldDeclaration(
result.Add(SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.ParseTypeName(fieldType),
SyntaxFactory.SeparatedList(new[] { SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier($"_{constructorArgument.Item2}")) })
))
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword));
result.Add(fieldDeclaration);
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)));
}

return result.ToArray();
return result;
}

/// <summary>
Expand Down

0 comments on commit 8a38a99

Please sign in to comment.