Update template working

This commit is contained in:
CodeLiturgy 2022-08-29 01:41:05 +01:00
parent f419c0f8ea
commit 654a54d5ef
8 changed files with 143 additions and 114 deletions

View File

@ -19,14 +19,12 @@ namespace MapTo
protected EfAddGeneratorContext( protected EfAddGeneratorContext(
Compilation compilation, Compilation compilation,
SourceGenerationOptions sourceGenerationOptions, SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax memberSyntax, MemberDeclarationSyntax memberSyntax)
string addSourceTemplate)
{ {
Compilation = compilation; Compilation = compilation;
_ignoredNamespaces = new(); _ignoredNamespaces = new();
Diagnostics = ImmutableArray<Diagnostic>.Empty; Diagnostics = ImmutableArray<Diagnostic>.Empty;
Usings = ImmutableArray.Create("System", Constants.RootNamespace); Usings = ImmutableArray.Create("System", Constants.RootNamespace);
SourceTemplate = addSourceTemplate;
SourceGenerationOptions = sourceGenerationOptions; SourceGenerationOptions = sourceGenerationOptions;
MemberSyntax = memberSyntax; MemberSyntax = memberSyntax;
@ -34,8 +32,7 @@ namespace MapTo
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis"); AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
} }
public string SourceTemplate { get; }
public ImmutableArray<Diagnostic> Diagnostics { get; private set; } public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
@ -54,12 +51,11 @@ namespace MapTo
public static EfAddGeneratorContext Create( public static EfAddGeneratorContext Create(
Compilation compilation, Compilation compilation,
SourceGenerationOptions sourceGenerationOptions, SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax typeSyntax, MemberDeclarationSyntax typeSyntax)
string addSourceTemplate)
{ {
EfAddGeneratorContext context = typeSyntax switch EfAddGeneratorContext context = typeSyntax switch
{ {
PropertyDeclarationSyntax => new EfAddGeneratorContext(compilation, sourceGenerationOptions, typeSyntax, addSourceTemplate), PropertyDeclarationSyntax => new EfAddGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@ -94,6 +90,13 @@ namespace MapTo
return sourceSymbol; return sourceSymbol;
} }
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = GetEntityStringLiteralSymbol(attributeData, semanticModel);
return sourceSymbol;
}
protected ImmutableArray<INamedTypeSymbol> GetEntityTypeSymbols(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null) protected ImmutableArray<INamedTypeSymbol> GetEntityTypeSymbols(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{ {
if (attributeSyntax is null) if (attributeSyntax is null)
@ -123,6 +126,25 @@ namespace MapTo
return resultList.ToImmutableArray(); return resultList.ToImmutableArray();
} }
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{
if (attributeSyntax is null)
{
return new ImmutableArray<LiteralExpressionSyntax>(){};
}
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
var sourceTypeExpressionSyntax = descendentNodes
.OfType<LiteralExpressionSyntax>()
.ToImmutableArray();
return sourceTypeExpressionSyntax;
}
protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult) protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult)
{ {
@ -194,6 +216,16 @@ namespace MapTo
return null; return null;
} }
var updateConfig = ExtractEfUpdateMethodsModel(semanticModel, entityTypeName, entityTypeFullName);
if (updateConfig == null)
{
AddDiagnostic(DiagnosticsFactory.MapFromAttributeNotFoundError(MemberSyntax.GetLocation()));
return null;
}
return new EfMethodsModel( return new EfMethodsModel(
SourceGenerationOptions, SourceGenerationOptions,
contextNamespace, contextNamespace,
@ -203,6 +235,7 @@ namespace MapTo
entityTypeFullName, entityTypeFullName,
entityTypeName, entityTypeName,
addConfig, addConfig,
updateConfig,
Usings); Usings);
} }
@ -233,6 +266,48 @@ namespace MapTo
return new EfAddMethodsModel(createTypeFullName, createTypeIdentifierName, returnTypeFullName, return new EfAddMethodsModel(createTypeFullName, createTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName); returnTypeIdentifierName);
} }
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, string entityTypeName, string entityTypeFullName)
{
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyPropertyName = GetEntityStringLiteralSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
if (efAddAttributeTypeSymbols == null) return null;
string updateTypeIdentifierName = entityTypeName;
string updateTypeFullName = entityTypeFullName;
string returnTypeIdentifierName = entityTypeName;
string returnTypeFullName = entityTypeFullName;
string keyPropertyTypeFullName = "int";
if (efAddAttributeTypeSymbols.Length > 0)
{
updateTypeIdentifierName = efAddAttributeTypeSymbols[0].Name;
updateTypeFullName = efAddAttributeTypeSymbols[0].ToDisplayString();
}
if (efAddAttributeTypeSymbols.Length > 0)
{
returnTypeIdentifierName = efAddAttributeTypeSymbols[1].Name;
returnTypeFullName = efAddAttributeTypeSymbols[1].ToDisplayString();
}
if (efAddAttributeTypeSymbols.Length > 2)
{
keyPropertyTypeFullName = efAddAttributeTypeSymbols[2].ToDisplayString();
}
return new EfUpdateMethodsModel(updateTypeFullName, updateTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName, keyPropertyName, keyPropertyTypeFullName);
}
private static ITypeSymbol GetEntityTypeData(MemberDeclarationSyntax memberDeclarationSyntax, SemanticModel? semanticModel = null) private static ITypeSymbol GetEntityTypeData(MemberDeclarationSyntax memberDeclarationSyntax, SemanticModel? semanticModel = null)
{ {

View File

@ -39,7 +39,9 @@ namespace MapTo
var options = SourceGenerationOptions.From(context); var options = SourceGenerationOptions.From(context);
var compilation = context.Compilation var compilation = context.Compilation
.AddSource(ref context, EfAddMethodsAttributeSource.Generate(options)); .AddSource(ref context, EfAddMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfUpdateMethodsAttributeSource.Generate(options));
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateMembers.Any()) if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateMembers.Any())
{ {
@ -59,17 +61,18 @@ namespace MapTo
foreach (var candidateMember in candidateMembers) foreach (var candidateMember in candidateMembers)
{ {
string addSourceTemplate = string.Empty; string addSourceTemplate = context.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains("AddToEntityTemplate"))?
if (context.AdditionalFiles.Length > 0)
{
addSourceTemplate = context.AdditionalFiles
.FirstOrDefault()?
.GetText()? .GetText()?
.ToString() ?? string.Empty; .ToString() ?? string.Empty;
}
string updateSourceTemplate = context.AdditionalFiles
var mappingContext = EfAddGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax, addSourceTemplate); .FirstOrDefault(x => x.Path.Contains("UpdateEntityTemplate"))?
.GetText()?
.ToString() ?? string.Empty;
var mappingContext = EfAddGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic); mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
@ -80,7 +83,7 @@ namespace MapTo
var (source, hintName) = candidateMember.MemberDeclarationSyntax switch var (source, hintName) = candidateMember.MemberDeclarationSyntax switch
{ {
PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate), PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate, updateSourceTemplate),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };

View File

@ -35,11 +35,10 @@ namespace MapTo.Extensions
{ {
var attributeLists = typeDeclarationSyntax.AttributeLists; var attributeLists = typeDeclarationSyntax.AttributeLists;
var selection = attributeLists var selection = attributeLists
.SelectMany(al => al.Attributes); .SelectMany(al => al.Attributes)
var result = selection .FirstOrDefault(x => x.Name.ToString().Contains(attributeName));
.FirstOrDefault();
return result; return selection;
} }
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) => public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attributeSymbol) =>

View File

@ -79,10 +79,12 @@ namespace MapTo
) : IEfMethodsConfiguration; ) : IEfMethodsConfiguration;
internal record EfUpdateMethodsModel( internal record EfUpdateMethodsModel(
string CreateTypeFullName, string UpdateTypeFullName,
string CreateTypeIdentifierName, string UpdateTypeIdentifierName,
string ReturnTypeFullName, string ReturnTypeFullName,
string ReturnTypeIdentifierName string ReturnTypeIdentifierName,
string keyPropertyName,
string keyFullTypeName
): IEfMethodsConfiguration; ): IEfMethodsConfiguration;
@ -95,6 +97,7 @@ namespace MapTo
string EntityTypeFullName, string EntityTypeFullName,
string EntityTypeIdentifierName, string EntityTypeIdentifierName,
EfAddMethodsModel AddMethodsModel, EfAddMethodsModel AddMethodsModel,
EfUpdateMethodsModel UpdateMethodsModel,
ImmutableArray<string> Usings ImmutableArray<string> Usings
); );

View File

@ -32,7 +32,7 @@ namespace MapTo.Sources
.WriteOpeningBracket(); .WriteOpeningBracket();
builder builder
.WriteLine($"public {AttributeName}Attribute(Type createDto = null, Type returnType = null)") .WriteLine($"public {AttributeName}Attribute(Type createType = null, Type returnType = null)")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteClosingBracket() .WriteClosingBracket()
.WriteLine(); .WriteLine();

View File

@ -9,7 +9,7 @@ namespace MapTo.Sources
{ {
internal static class EfMethodsSource internal static class EfMethodsSource
{ {
internal static SourceCode Generate(EfMethodsModel model, string addSourceTemplate) internal static SourceCode Generate(EfMethodsModel model, string addSourceTemplate, string updateSourceTemplate)
{ {
using var builder = new SourceBuilder(); using var builder = new SourceBuilder();
@ -21,6 +21,10 @@ namespace MapTo.Sources
builder builder
.WriteUsings(model.Usings) .WriteUsings(model.Usings)
.WriteLine("using Microsoft.EntityFrameworkCore;") .WriteLine("using Microsoft.EntityFrameworkCore;")
.WriteLine("using System.Linq;")
.WriteLine("using System.Linq.Expressions;")
.WriteLine("using System.Threading.Tasks;")
.WriteLine() .WriteLine()
// Namespace declaration // Namespace declaration
.WriteLine($"namespace {model.Namespace}") .WriteLine($"namespace {model.Namespace}")
@ -36,6 +40,12 @@ namespace MapTo.Sources
contextFullName, contextFullName,
entityTypeFullName, entityTypeFullName,
propertyName) propertyName)
.EfAddUpdateEntityMethod(model, updateSourceTemplate,
entityTypeName,
contextFullName,
entityTypeFullName,
propertyName)
// End class declaration // End class declaration
.WriteClosingBracket() .WriteClosingBracket()
.WriteLine() .WriteLine()
@ -93,45 +103,53 @@ namespace MapTo.Sources
return builder; return builder;
} }
private static SourceBuilder EfAddUpdateEntityMethod(this SourceBuilder builder, EfMethodsModel model, string addSourceTemplate, string entityTypeName, private static SourceBuilder EfAddUpdateEntityMethod(this SourceBuilder builder, EfMethodsModel model, string updateSourceTemplate, string entityTypeName,
string contextFullName, string entityTypeFullName, string propertyName) string contextFullName, string entityTypeFullName, string propertyName)
{ {
var returnTypeFullName = model.AddMethodsModel.ReturnTypeFullName; var returnTypeFullName = model.UpdateMethodsModel.ReturnTypeFullName;
var updateTypeFullName = model.AddMethodsModel.CreateTypeFullName; var updateTypeFullName = model.UpdateMethodsModel.UpdateTypeFullName;
var newEntityVarName = $"new{model.EntityTypeIdentifierName}"; var updateVarName = $"{model.EntityTypeIdentifierName.ToCamelCase()}ToUpdate";
var toCreateVarName = $"{model.EntityTypeIdentifierName.ToCamelCase()}ToCreate"; var keyPropertyName = model.UpdateMethodsModel.keyPropertyName;
var keyTypeFullName = model.UpdateMethodsModel.keyFullTypeName;
if (!addSourceTemplate.IsEmpty()) var existingVarName = entityTypeName.ToCamelCase();
var keyVarName = entityTypeName.ToCamelCase() + model.UpdateMethodsModel.keyPropertyName;
if (!updateSourceTemplate.IsEmpty())
{ {
var templateToSourceBuilder = new StringBuilder(addSourceTemplate); var templateToSourceBuilder = new StringBuilder(updateSourceTemplate);
templateToSourceBuilder templateToSourceBuilder
.Replace("{entityTypeName}", entityTypeName) .Replace("{entityTypeName}", entityTypeName)
.Replace("{returnTypeFullName}", returnTypeFullName) .Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{updateTypeFullName}", updateTypeFullName) .Replace("{updateTypeFullName}", updateTypeFullName)
.Replace("{updateVarName}", updateVarName)
.Replace("{contextFullName}", contextFullName) .Replace("{contextFullName}", contextFullName)
.Replace("{propertyName}", propertyName); .Replace("{propertyName}", propertyName)
.Replace("{keyTypeFullName}", keyTypeFullName)
.Replace("{keyPropertyName}", keyPropertyName)
.Replace("{keyVarName}", keyVarName)
.Replace("{existingEntityVarName}", existingVarName);
builder builder
.Write(templateToSourceBuilder.ToString()); .Write(templateToSourceBuilder.ToString());
} }
if (addSourceTemplate.IsEmpty()) if (updateSourceTemplate.IsEmpty())
{ {
builder builder
.WriteLine(GeneratedFilesHeader) .WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {model.AddMethodsModel.ReturnTypeFullName}) Add{entityTypeName}(") .WriteLine($"public static (bool, {returnTypeFullName}) Update{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,") .WriteLine($"this {contextFullName} dbContext,")
.WriteLine($"{updateTypeFullName} {toCreateVarName})") .WriteLine($"{updateTypeFullName} {updateVarName},")
.WriteLine($"{keyTypeFullName} {keyVarName})")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteLine($"var {newEntityVarName} = new {model.EntityTypeFullName}({toCreateVarName});") .WriteLine($"var {existingVarName} = dbContext.{propertyName}.FirstOrDefault(x => x.{keyPropertyName} == {keyVarName});")
.WriteLine($"dbContext.{propertyName}.Add({newEntityVarName});") .WriteLine($"if ({existingVarName} == null) return (false, null);")
.WriteLine($"var success = dbContext.SaveChanges() >= 0;") .WriteLine($"var success = dbContext.SaveChanges() >= 0;")
.WriteLine($"return (success, new {returnTypeFullName}({newEntityVarName}));") .WriteLine($"return (success, new {returnTypeFullName}({existingVarName}));")
.WriteClosingBracket(); .WriteClosingBracket();
builder builder
.WriteLine(); .WriteLine();
} }
builder.Indent();
return builder; return builder;
} }

View File

@ -31,7 +31,7 @@ namespace MapTo.Sources
.WriteOpeningBracket(); .WriteOpeningBracket();
builder builder
.WriteLine($"public {AttributeName}Attribute(Type updateType = null, Type returnType = null, string keyPropertyMemberName, Type keyPropertyMemberType)") .WriteLine($"public {AttributeName}Attribute(Type updateType, Type returnType, string keyPropertyMemberName, Type keyPropertyMemberType)")
.WriteOpeningBracket() .WriteOpeningBracket()
.WriteClosingBracket() .WriteClosingBracket()
.WriteLine(); .WriteLine();

View File

@ -1,69 +0,0 @@
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
/// <summary>
/// UpdateData Attribute Source
/// </summary>
public class UpdateDataAttributeSource
{
internal const string AttributeName = "UpdateData";
internal const string AttributeClassName = AttributeName + "Attribute";
internal const string FullyQualifiedName = RootNamespace + "." + AttributeClassName;
internal static SourceCode Generate(SourceGenerationOptions options)
{
using var builder = new SourceBuilder()
.WriteLine(GeneratedFilesHeader)
.WriteLine("using System;")
.WriteLine()
.WriteLine($"namespace {RootNamespace}")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Generates CRUD functions to be used with the specified database context and entity.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine($"/// Initializes a new instance of the <see cref=\"{AttributeName}Attribute\"/> class with the specified <paramref name=\"sourceType\"/>.")
.WriteLine("/// </summary>")
.WriteLine("/// <param name=\"sourceType\">The type of to map from.</param>");
}
builder
.WriteLine($"public {AttributeName}Attribute(Type databaseContextType)")
.WriteOpeningBracket()
.WriteLine("DatabaseContextType = databaseContextType;")
.WriteClosingBracket()
.WriteLine();
if (options.GenerateXmlDocument)
{
builder
.WriteLine("/// <summary>")
.WriteLine("/// Gets the type to map from.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("public Type DatabaseContextType { get; }")
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}