Remove EfGenerator, migrated to separate project

This commit is contained in:
CodeLiturgy 2022-09-05 23:18:15 +01:00
parent b8637c94ab
commit 4e80156708
15 changed files with 1 additions and 1330 deletions

View File

@ -1,41 +0,0 @@
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
public class EfGeneratorAttributeSource
{
internal const string AttributeName = "EfGenerator";
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("/// Generate Add methods for interacting with the entity")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Class)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket()
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal static class EfGetManyAttributeSource
{
internal const string AttributeName = "EfGetMany";
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("/// Attribute for generating a function to get one EF entity with a predicate as argument and a return type for the projection.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type returnType){"{}"}")
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal class EfGetOneByAttributeSource
{
internal const string AttributeName = "EfGetOneBy";
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("/// Generate Add methods for interacting with the entity")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(string bynameof, Type returnType)")
.WriteOpeningBracket()
.WriteClosingBracket()
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,46 +0,0 @@
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
public class EfUpdateMethodsAttributeSource
{
internal const string AttributeName = "EfUpdateMethods";
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("/// Generate Add methods for interacting with the entity")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type updateType, Type returnType, string keyPropertyMemberName)")
.WriteOpeningBracket()
.WriteClosingBracket()
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal static class EfGetOneAttributeSource
{
internal const string AttributeName = "EfGetOne";
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("/// Attribute for generating a function to get one EF entity with a predicate as argument and a return type for the projection.")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type returnType){"{}"}")
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,45 +0,0 @@
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal static class EfAddMethodsAttributeSource
{
internal const string AttributeName = "EfAddMethods";
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("/// Generate Add methods for interacting with the entity")
.WriteLine("/// </summary>");
}
builder
.WriteLine("[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]")
.WriteLine($"public sealed class {AttributeName}Attribute : Attribute")
.WriteOpeningBracket();
builder
.WriteLine($"public {AttributeName}Attribute(Type createType = null, Type returnType = null){"{}"}")
.WriteLine();
builder
.WriteClosingBracket() // class
.WriteClosingBracket(); // namespace
return new(builder.ToString(), $"{AttributeName}Attribute.g.cs");
}
}
}

View File

@ -1,495 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MapTo.Extensions;
using MapTo.Sources;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSharpExtensions = Microsoft.CodeAnalysis.CSharpExtensions;
#pragma warning disable CS8602
namespace MapTo
{
internal class EfGeneratorContext
{
private readonly List<SymbolDisplayPart> _ignoredNamespaces;
public ImmutableArray<Diagnostic> Diagnostics { get; private set; }
public EfMethodsModel? Model { get; private set; }
protected Compilation Compilation { get; }
protected INamedTypeSymbol MappingContextTypeSymbol { get; }
protected SourceGenerationOptions SourceGenerationOptions { get; }
protected TypeDeclarationSyntax TypeSyntax { get; }
protected ImmutableArray<string> Usings { get; private set; }
protected INamedTypeSymbol EfUpdateMethodsTypeSymbol { get; }
protected INamedTypeSymbol EfAddMethodsTypeSymbol { get; }
protected EfGeneratorContext(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
TypeDeclarationSyntax typeSyntax)
{
Compilation = compilation;
_ignoredNamespaces = new();
Diagnostics = ImmutableArray<Diagnostic>.Empty;
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
SourceGenerationOptions = sourceGenerationOptions;
TypeSyntax = typeSyntax;
EfUpdateMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfUpdateMethodsAttributeSource.FullyQualifiedName);
EfAddMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfAddMethodsAttributeSource.FullyQualifiedName);
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
}
public static EfGeneratorContext Create(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
TypeDeclarationSyntax typeSyntax)
{
EfGeneratorContext context = typeSyntax switch
{
ClassDeclarationSyntax => new EfGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
_ => throw new ArgumentOutOfRangeException()
};
context.Model = context.CreateMappingModel();
return context;
}
protected void AddDiagnostic(Diagnostic diagnostic)
{
Diagnostics = Diagnostics.Add(diagnostic);
}
protected void AddUsingIfRequired(ISymbol? namedTypeSymbol) =>
AddUsingIfRequired(namedTypeSymbol?.ContainingNamespace.IsGlobalNamespace == false, namedTypeSymbol?.ContainingNamespace);
protected void AddUsingIfRequired(bool condition, INamespaceSymbol? ns) =>
AddUsingIfRequired(condition && ns is not null && !_ignoredNamespaces.Contains(ns.ToDisplayParts().First()), ns?.ToDisplayString());
protected void AddUsingIfRequired(bool condition, string? ns)
{
if (ns is not null && condition && ns != TypeSyntax.GetNamespace() && !Usings.Contains(ns))
{
Usings = Usings.Add(ns);
}
}
protected ImmutableArray<INamedTypeSymbol> GetEntityTypeSymbol(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = GetEntityTypeSymbols(attributeData, semanticModel);
return sourceSymbol;
}
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = GetEntityStringLiteralSymbol(attributeData);
return sourceSymbol;
}
protected TypeInfo FindSecondTypeInfoOfMemberExpressionSyntax(SemanticModel semanticModel, SyntaxNode attributeSyntax)
{
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
var expression = descendentNodes.OfType<MemberAccessExpressionSyntax>().FirstOrDefault()?.Expression;
if (expression != null)
{
return semanticModel.GetTypeInfo(expression);
}
return new TypeInfo();
}
protected TypeInfo? FindFirstTypeInfoOfMemberExpressionSyntax(SemanticModel semanticModel, SyntaxNode attributeSyntax)
{
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
// Get the type of the key member id
var expression = descendentNodes.OfType<MemberAccessExpressionSyntax>().FirstOrDefault();
if (expression != null)
{
return semanticModel.GetTypeInfo(expression);
}
return null;
}
protected string ExtractNameOfMemberName(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = ExtractNameOfMemberName(attributeData, semanticModel);
return sourceSymbol;
}
protected ImmutableArray<INamedTypeSymbol> GetEntityTypeSymbols(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null)
{
if (attributeSyntax is null)
{
return new ImmutableArray<INamedTypeSymbol>(){};
}
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
var sourceTypeExpressionSyntax = descendentNodes
.OfType<TypeOfExpressionSyntax>()
.ToImmutableArray();
// cast
var resultList = new List<INamedTypeSymbol>();
for (int i = 0; i < sourceTypeExpressionSyntax.Length; i++)
{
var sourceTypeExpression = sourceTypeExpressionSyntax[i];
if (semanticModel.GetTypeInfo(sourceTypeExpression.Type).Type is INamedTypeSymbol namedTypeSymbol)
{
resultList.Add(namedTypeSymbol);
}
}
return resultList.ToImmutableArray();
}
protected ImmutableArray<LiteralExpressionSyntax> GetEntityStringLiteralSymbol(SyntaxNode? attributeSyntax)
{
if (attributeSyntax is null)
{
return new ImmutableArray<LiteralExpressionSyntax>(){};
}
var descendentNodes = attributeSyntax
.DescendantNodes();
var sourceTypeExpressionSyntax = descendentNodes
.OfType<LiteralExpressionSyntax>()
.ToImmutableArray();
return sourceTypeExpressionSyntax;
}
protected string ExtractNameOfMemberName(SyntaxNode? attributeSyntax, SemanticModel semanticModel)
{
if (attributeSyntax is null)
{
return string.Empty;
}
var descendentNodes = attributeSyntax
.DescendantNodes();
var idNode = descendentNodes.OfType<InvocationExpressionSyntax>()
.FirstOrDefault()
?.ChildNodesAndTokens()
.FirstOrDefault(x => x.IsKind(SyntaxKind.ArgumentList))
.ChildNodesAndTokens()
.FirstOrDefault(x => x.IsKind(SyntaxKind.Argument))
.ChildNodesAndTokens()
.FirstOrDefault()
.ChildNodesAndTokens()
.LastOrDefault();
return idNode != null ? idNode.ToString() : String.Empty;
}
protected bool IsEnumerable(ISymbol property, out INamedTypeSymbol? namedTypeSymbolResult)
{
if (!property.TryGetTypeSymbol(out var propertyType))
{
AddDiagnostic(DiagnosticsFactory.NoMatchingPropertyTypeFoundError(property));
namedTypeSymbolResult = null;
return false;
}
if (
propertyType is INamedTypeSymbol namedTypeSymbol &&
!propertyType.IsPrimitiveType() &&
(Compilation.IsGenericEnumerable(propertyType) || propertyType.AllInterfaces.Any(i => Compilation.IsGenericEnumerable(i))))
{
namedTypeSymbolResult = namedTypeSymbol;
return true;
}
namedTypeSymbolResult = null;
return false;
}
private ImmutableArray<MemberDeclarationSyntax> GetAllowedMemberSyntaxes(string attributeName)
{
var classDeclarationSyntax = TypeSyntax;
var syntaxes = classDeclarationSyntax.DescendantNodes()
.OfType<MemberDeclarationSyntax>()
.Where(x => x
.DescendantNodes()
.OfType<AttributeSyntax>().Any(syntax =>
syntax.Name.ToString() == attributeName))
.ToImmutableArray();
return syntaxes;
}
private EfMethodsModel? CreateMappingModel()
{
var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
// get containing class type information
ClassDeclarationSyntax classDeclarationSyntax = TypeSyntax as ClassDeclarationSyntax;
// context name
var dbContextName = classDeclarationSyntax.Identifier.ValueText;
var namespaceDeclaration = classDeclarationSyntax.Parent as NamespaceDeclarationSyntax;
var contextNamespace = namespaceDeclaration.Name.ToString();
var contextTypeFullName = $"{contextNamespace}.{dbContextName}";
// handles different attributes
var addAttributeSymbols = GetAllowedMemberSyntaxes(EfAddMethodsAttributeSource.AttributeName);
var updateAttributesMembers = GetAllowedMemberSyntaxes(EfUpdateMethodsAttributeSource.AttributeName);
var getOneAttributesMembers = GetAllowedMemberSyntaxes(EfGetOneByAttributeSource.AttributeName);
var getOneWithAttributesMembers = GetAllowedMemberSyntaxes(EfGetOneAttributeSource.AttributeName);
var getManyWithAttributesMembers = GetAllowedMemberSyntaxes(EfGetManyAttributeSource.AttributeName);
List<EfEntityDataModel> methodsModels = new List<EfEntityDataModel>();
foreach (var uProperty in getOneAttributesMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetEntityByModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in addAttributeSymbols)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newAddModel = ExtractEfAddMethodsModel(semanticModel, uProperty, entityDataModel);
methodsModels.Add(newAddModel);
}
foreach (var uProperty in updateAttributesMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfUpdateMethodsModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in getOneWithAttributesMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetEntityWithModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in getManyWithAttributesMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetManyModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
//SpinWait.SpinUntil(() => Debugger.IsAttached);
return new EfMethodsModel(
SourceGenerationOptions,
contextNamespace,
dbContextName, contextTypeFullName,
methodsModels.ToImmutableArray(),
Usings);
}
private static EfEntityDataModel GetEntityData(MemberDeclarationSyntax uProperty, SemanticModel semanticModel)
{
var entityTypeData = GetEntityTypeData(uProperty, semanticModel);
string entityIdentifierName = entityTypeData.Name;
string entityFullName = entityTypeData.ToDisplayString();
var propertyNamex = (uProperty as PropertyDeclarationSyntax).Identifier.ValueText;
return new EfEntityDataModel(propertyNamex, entityFullName, entityIdentifierName);
}
private EfAddMethodsModel ExtractEfAddMethodsModel(SemanticModel semanticModel, MemberDeclarationSyntax memberSyntax, EfEntityDataModel efEntityDataModel)
{
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(memberSyntax, EfAddMethodsAttributeSource.AttributeName, semanticModel);
string createTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName;
string createTypeFullName = efEntityDataModel.EntityTypeFullName;
string returnTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = efEntityDataModel.EntityTypeFullName;
if (efAddAttributeTypeSymbols.Length > 0)
{
createTypeIdentifierName = efAddAttributeTypeSymbols[0].Name;
createTypeFullName = efAddAttributeTypeSymbols[0].ToDisplayString();
}
if (efAddAttributeTypeSymbols.Length > 1)
{
returnTypeIdentifierName = efAddAttributeTypeSymbols[1].Name;
returnTypeFullName = efAddAttributeTypeSymbols[1].ToDisplayString();
}
return new EfAddMethodsModel(efEntityDataModel, createTypeFullName, createTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName);
}
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(uProperty, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyPropertyName = ExtractNameOfMemberName(uProperty, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyMemberType = FindFirstTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
//var secondTypeInfo = FindSecondTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
var keyMemberFullName = keyMemberType.Value.Type.ToDisplayString();
//var keyMemberName = keyMemberType.Value.Type.Name;
// Try grabbing string literal if there's no nameof in it
if (keyPropertyName == string.Empty)
{
keyPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
}
if (efAddAttributeTypeSymbols == null) return null;
string updateTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string updateTypeFullName = entityDataModel.EntityTypeFullName;
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
if (efAddAttributeTypeSymbols.Length > 0)
{
updateTypeIdentifierName = efAddAttributeTypeSymbols[0].Name;
updateTypeFullName = efAddAttributeTypeSymbols[0].ToDisplayString();
}
// Grab return type from attribute argument
if (efAddAttributeTypeSymbols.Length > 1)
{
returnTypeIdentifierName = efAddAttributeTypeSymbols[1].Name;
returnTypeFullName = efAddAttributeTypeSymbols[1].ToDisplayString();
}
return new EfUpdateMethodsModel(entityDataModel, updateTypeFullName, updateTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName, keyPropertyName, keyMemberFullName);
}
private EfGetOneWithModel ExtractEfGetEntityWithModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
var efTypeofSymbols =GetEntityTypeSymbol(uProperty, EfGetOneByAttributeSource.AttributeName, semanticModel);
if (efTypeofSymbols == null) return null;
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
// Grab return type from attribute argument
if (efTypeofSymbols.Length > 0)
{
returnTypeIdentifierName = efTypeofSymbols[0].Name;
returnTypeFullName = efTypeofSymbols[0].ToDisplayString();
}
return new EfGetOneWithModel(entityDataModel, returnTypeFullName, returnTypeIdentifierName);
}
private EfGetManyModel ExtractEfGetManyModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
var efTypeofSymbols =GetEntityTypeSymbol(uProperty, EfGetOneByAttributeSource.AttributeName, semanticModel);
if (efTypeofSymbols == null) return null;
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
// Grab return type from attribute argument
if (efTypeofSymbols.Length > 0)
{
returnTypeIdentifierName = efTypeofSymbols[0].Name;
returnTypeFullName = efTypeofSymbols[0].ToDisplayString();
}
return new EfGetManyModel(entityDataModel, returnTypeFullName, returnTypeIdentifierName);
}
private EfGetOneByModel ExtractEfGetEntityByModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
var efTypeofSymbols = GetEntityTypeSymbol(uProperty, EfGetOneByAttributeSource.AttributeName, semanticModel);
var byParamPropertyName = ExtractNameOfMemberName(uProperty, EfGetOneByAttributeSource.AttributeName, semanticModel);
var keyMemberType = FindFirstTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
var byParamFullTypeName = keyMemberType.Value.Type.ToDisplayString();
// Try grabbing string literal if there's no nameof in it
if (byParamPropertyName == string.Empty)
{
byParamPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfGetOneByAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
}
if (efTypeofSymbols == null) return null;
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
// Grab return type from attribute argument
if (efTypeofSymbols.Length > 0)
{
returnTypeIdentifierName = efTypeofSymbols[0].Name;
returnTypeFullName = efTypeofSymbols[0].ToDisplayString();
}
return new EfGetOneByModel(entityDataModel, byParamPropertyName, byParamFullTypeName, returnTypeFullName, returnTypeIdentifierName);
}
private static ITypeSymbol GetEntityTypeData(MemberDeclarationSyntax memberDeclarationSyntax, SemanticModel? semanticModel = null)
{
var member = (memberDeclarationSyntax as PropertyDeclarationSyntax).Type;
var genericSyntax = member as GenericNameSyntax;
var typeInfo = semanticModel.GetTypeInfo( genericSyntax);
var firstTypeArgument = (typeInfo.ConvertedType as INamedTypeSymbol).TypeArguments[0];
return firstTypeArgument;
}
}
}

View File

@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MapTo.Extensions;
using MapTo.Sources;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace MapTo
{
/// <summary>
/// Ef methods source generator
/// </summary>
[Generator]
public class EfMethodsGenerator: ISourceGenerator
{
/// <inheritdoc />
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new EfMethodsSyntaxReceiver());
}
/// <inheritdoc />
public void Execute(GeneratorExecutionContext context)
{
try
{
var options = SourceGenerationOptions.From(context);
var compilation = context.Compilation
.AddSource(ref context, EfGeneratorAttributeSource.Generate(options))
.AddSource(ref context, EfAddMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfUpdateMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfGetOneByAttributeSource.Generate(options))
.AddSource(ref context, EfGetOneAttributeSource.Generate(options))
.AddSource(ref context, EfGetManyAttributeSource.Generate(options));
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateTypes.Any())
{
AddGeneratedExtensions(context, compilation, receiver.CandidateTypes, options);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
private static void AddGeneratedExtensions(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateMembers, SourceGenerationOptions options)
{
string addSourceTemplate = GetCsxMethodTemplate(context, "AddToEntityTemplate");
string updateSourceTemplate = GetCsxMethodTemplate(context, "UpdateEntityTemplate");
string getOneByTemplate = GetCsxMethodTemplate(context, "GetOneByTemplate");
string getOneWithByTemplate = GetCsxMethodTemplate(context, "GetOneTemplate");
string getManyWithTemplate = GetCsxMethodTemplate(context, "GetManyTemplate");
foreach (var candidateMember in candidateMembers)
{
var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
if (mappingContext.Model is null)
{
continue;
}
var (source, hintName) = candidateMember switch
{
ClassDeclarationSyntax => EfMethodsSource.Generate(
mappingContext.Model,
addSourceTemplate,
updateSourceTemplate,
getOneByTemplate,
getOneWithByTemplate,
getManyWithTemplate),
_ => throw new ArgumentOutOfRangeException()
};
context.AddSource(hintName, source);
}
}
private static string GetCsxMethodTemplate(GeneratorExecutionContext context, string templateName)
{
return context
.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains(templateName))?
.GetText()?
.ToString() ?? string.Empty;
}
}
}

View File

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MapTo.Extensions;
using Microsoft.CodeAnalysis.CSharp;
using static MapTo.Sources.Constants;
namespace MapTo.Sources
{
internal static class EfMethodsSource
{
internal static SourceCode Generate(EfMethodsModel model,
string addSourceTemplate,
string updateSourceTemplate,
string getOneBySourceTemplate,
string getOneWithSourceTemplate,
string getManyWithSourceTemplate
)
{
using var builder = new SourceBuilder();
var contextFullName = model.ContextFullType;
builder
.WriteLine(GeneratedFilesHeader)
.WriteUsings(model.Usings)
.WriteLine("using Microsoft.EntityFrameworkCore;")
.WriteLine("using System.Linq;")
.WriteLine("using System.Linq.Expressions;")
.WriteLine("using System.Threading.Tasks;")
.WriteLine()
// Namespace declaration
.WriteLine($"namespace {model.Namespace}")
.WriteOpeningBracket()
// Class declaration
.WriteLine($"public static partial class {model.ContextTypeName}Extensions")
.WriteOpeningBracket()
.WriteLine();
foreach (var methodModel in model.MethodsModels.OrderBy(x => x.PropertyName))
{
switch (methodModel)
{
case EfAddMethodsModel modl:
builder
.EfAddAddEntityMethod(model, modl, addSourceTemplate)
.WriteLine();
break;
case EfUpdateMethodsModel modl:
builder
.EfAddUpdateEntityMethod(model, modl, updateSourceTemplate)
.WriteLine();
break;
case EfGetOneByModel modl:
builder
.EfAddGetOneEntityByMethod(model, modl, getOneBySourceTemplate)
.WriteLine();
break;
case EfGetManyModel modl:
builder
.EfAddGetManyWithMethod(model, modl, getManyWithSourceTemplate)
.WriteLine();
break;
case EfGetOneWithModel modl:
builder
.EfAddGetOneEntityWithMethod(model, modl, getOneWithSourceTemplate)
.WriteLine();
break;
}
}
builder
// End class declaration
.WriteClosingBracket()
.WriteLine()
// End namespace declaration
.WriteClosingBracket();
var generatedCode = builder.ToString();
var hintName = $"{model.ContextTypeName}Extensions.g.cs";
return new(generatedCode, hintName);
}
internal static SourceBuilder ParseTemplate(this SourceBuilder builder, string finalTemplate)
{
string[] array = finalTemplate.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < array.Length; i++)
{
var line = array[i];
builder.WriteLine(line);
}
return builder;
}
}
}

View File

@ -1,60 +0,0 @@
using MapTo.Extensions;
using System;
using System.Collections.Generic;
using System.Text;
namespace MapTo.Sources
{
internal static class EfAddEntityTemplateSource
{
internal static SourceBuilder EfAddAddEntityMethod(this SourceBuilder builder, EfMethodsModel methodsModel, EfAddMethodsModel model, string addSourceTemplate)
{
var returnTypeFullName = model.ReturnTypeFullName;
var createTypeFullName = model.CreateTypeFullName;
var newEntityVarName = $"new{model.EntityTypeIdentifierName}";
var toCreateVarName = $"{model.EntityTypeIdentifierName.ToCamelCase()}ToCreate";
var entityTypeName = model.EntityTypeIdentifierName;
var contextFullName = methodsModel.ContextFullType;
var propertyName = model.PropertyName;
if (!addSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(addSourceTemplate);
templateToSourceBuilder
.Replace("{entityTypeName}", model.EntityTypeIdentifierName)
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{createTypeFullName}", createTypeFullName)
.Replace("{contextFullName}", contextFullName)
.Replace("{toCreateVarName}", toCreateVarName)
.Replace("{newEntityVarName}", newEntityVarName)
.Replace("{entityTypeFullName}", model.EntityTypeFullName)
.Replace("{propertyName}", propertyName);
builder
.ParseTemplate(templateToSourceBuilder.ToString());
}
if (addSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
//.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {model.ReturnTypeFullName}) Add{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,")
.WriteLine($"{createTypeFullName} {toCreateVarName})")
.WriteOpeningBracket()
.WriteLine($"var {newEntityVarName} = new {model.EntityTypeFullName}({toCreateVarName});")
.WriteLine($"dbContext.{propertyName}.Add({newEntityVarName});")
.WriteLine($"var success = dbContext.SaveChanges() >= 0;")
.WriteLine($"return (success, new {returnTypeFullName}({newEntityVarName}));")
.WriteClosingBracket();
builder
.WriteLine();
}
return builder;
}
}
}

View File

@ -1,63 +0,0 @@
using MapTo.Extensions;
using MapTo.Sources;
using System;
using System.Collections.Generic;
using System.Text;
namespace MapTo
{
internal static class EfGetOneEntityByTemplate
{
internal static SourceBuilder EfAddGetOneEntityByMethod(this SourceBuilder builder, EfMethodsModel methodsModel, EfGetOneByModel model, string addSourceTemplate)
{
var returnTypeFullName = model.ReturnTypeFullName;
var entityTypeName = model.EntityTypeIdentifierName;
var byParamPropertyName = model.ByParamPropertyName;
var byParamFullType = model.ByParamFullTypeName;
var contextFullName = methodsModel.ContextFullType;
var byParamVarName = model.ByParamPropertyName.ToCamelCase();
var findEntityVarName = model.EntityTypeIdentifierName.ToCamelCase();
var propertyName = model.PropertyName;
if (!addSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(addSourceTemplate);
templateToSourceBuilder
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{entityTypeName}", entityTypeName)
.Replace("{byParamPropertyName}", byParamPropertyName)
.Replace("{contextFullName}", contextFullName)
.Replace("{contextFullName}", contextFullName)
.Replace("{byParamFullType}", byParamFullType)
.Replace("{byParamVarName}", byParamVarName)
.Replace("{findEntityVarName}", findEntityVarName)
.Replace("{propertyName}", propertyName);
builder
.ParseTemplate(templateToSourceBuilder.ToString());
}
if (addSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
//.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {returnTypeFullName}) GetOne{entityTypeName}By{byParamPropertyName} (this {contextFullName} dbContext, {byParamFullType} {byParamVarName})")
.WriteOpeningBracket()
.WriteLine($"var {findEntityVarName} = dbContext.{propertyName}")
.WriteLine($".Where(x => x.{byParamPropertyName} == {byParamVarName})")
.WriteLine($".Select(x => new {returnTypeFullName}(x))")
.WriteLine($".FirstOrDefault();")
.WriteLine($"return ({findEntityVarName} != null, {findEntityVarName});")
.WriteClosingBracket();
builder
.WriteLine();
}
return builder;
}
}
}

View File

@ -1,107 +0,0 @@
using MapTo.Extensions;
using MapTo.Sources;
using System;
using System.Collections.Generic;
using System.Text;
namespace MapTo
{
internal static class EfGetOneWithTemplateSource
{
internal static SourceBuilder EfAddGetOneEntityWithMethod(this SourceBuilder builder, EfMethodsModel methodsModel, EfGetOneWithModel model, string addSourceTemplate)
{
var returnTypeFullName = model.ReturnTypeFullName;
var entityTypeName = model.EntityTypeIdentifierName;
var contextFullName = methodsModel.ContextFullType;
var findEntityVarName = model.EntityTypeIdentifierName.ToCamelCase();
var propertyName = model.PropertyName;
if (!addSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(addSourceTemplate);
templateToSourceBuilder
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{entityTypeName}", entityTypeName)
.Replace("{contextFullName}", contextFullName)
.Replace("{contextFullName}", contextFullName)
.Replace("{findEntityVarName}", findEntityVarName)
.Replace("{propertyName}", propertyName);
builder
.ParseTemplate(templateToSourceBuilder.ToString());
}
if (addSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
//.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {returnTypeFullName}) GetOne{entityTypeName}With (this {contextFullName} dbContext, Expression<Func<{returnTypeFullName},bool>> with) ")
.WriteOpeningBracket()
.WriteLine($"var {findEntityVarName} =")
.WriteLine($".Select(x => new {returnTypeFullName}(x))")
.WriteLine($".FirstOrDefault(with);")
.WriteLine()
.WriteLine($"return ({findEntityVarName} != null, {findEntityVarName});")
.WriteClosingBracket();
builder
.WriteLine();
}
return builder;
}
internal static SourceBuilder EfAddGetManyWithMethod(this SourceBuilder builder, EfMethodsModel methodsModel, EfGetManyModel model, string addSourceTemplate)
{
var returnTypeFullName = model.ReturnTypeFullName;
var entityTypeName = model.EntityTypeIdentifierName;
var contextFullName = methodsModel.ContextFullType;
var findEntityVarName = model.EntityTypeIdentifierName.ToCamelCase();
var propertyName = model.PropertyName;
if (!addSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(addSourceTemplate);
templateToSourceBuilder
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{entityTypeName}", entityTypeName)
.Replace("{contextFullName}", contextFullName)
.Replace("{contextFullName}", contextFullName)
.Replace("{findEntityVarName}", findEntityVarName)
.Replace("{propertyName}", propertyName);
builder
.ParseTemplate(templateToSourceBuilder.ToString());
}
if (addSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
//.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, ImmutableArray<{returnTypeFullName}>) Get{propertyName}(this {contextFullName}dbContext, Expression <Func<{returnTypeFullName}, bool> > where = null,")
.WriteLine($"Expression <Func<{returnTypeFullName}, object> > orderBy = null, int skip = 0, int take = 50, int orderDir = 1)")
.WriteOpeningBracket()
.WriteLine($"var query = dbContext")
.WriteLine($".{propertyName}")
.WriteLine($".Select(x => new {returnTypeFullName}(x));")
.WriteComment("limit take by 200 records")
.WriteLine("if (take > 200) take = 200;")
.WriteLine("query.Skip(skip).Take(take);")
.WriteLine("if (where != null) query.Where(where);")
.WriteLine("if (orderDir == 1) query.OrderBy(orderBy);")
.WriteLine("else query.OrderByDescending(orderBy);")
.WriteLine($"return query.ToImmutableArray();")
.WriteClosingBracket();
builder
.WriteLine();
}
return builder;
}
}
}

View File

@ -1,62 +0,0 @@
using MapTo.Extensions;
using System;
using System.Collections.Generic;
using System.Text;
namespace MapTo.Sources
{
internal static class EfUpdateEntityTemplateSource
{
internal static SourceBuilder EfAddUpdateEntityMethod(this SourceBuilder builder, EfMethodsModel methodsModel, EfUpdateMethodsModel model, string updateSourceTemplate)
{
var entityTypeName = model.EntityTypeIdentifierName;
var contextFullName = methodsModel.ContextFullType;
var propertyName = model.PropertyName;
var returnTypeFullName = model.ReturnTypeFullName;
var updateTypeFullName = model.UpdateTypeFullName;
var updateVarName = $"{entityTypeName.ToCamelCase()}ToUpdate";
var keyPropertyName = model.KeyPropertyName;
var keyTypeFullName = model.KeyFullTypeName;
var existingVarName = entityTypeName.ToCamelCase();
var keyVarName = entityTypeName.ToCamelCase() + model.KeyPropertyName;
if (!updateSourceTemplate.IsEmpty())
{
var templateToSourceBuilder = new StringBuilder(updateSourceTemplate);
templateToSourceBuilder
.Replace("{entityTypeName}", entityTypeName)
.Replace("{returnTypeFullName}", returnTypeFullName)
.Replace("{updateTypeFullName}", updateTypeFullName)
.Replace("{updateVarName}", updateVarName)
.Replace("{contextFullName}", contextFullName)
.Replace("{propertyName}", propertyName)
.Replace("{keyTypeFullName}", keyTypeFullName)
.Replace("{keyPropertyName}", keyPropertyName)
.Replace("{keyVarName}", keyVarName)
.Replace("{existingEntityVarName}", existingVarName);
builder
.ParseTemplate(templateToSourceBuilder.ToString());
}
if (updateSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
.WriteLine($"public static (bool, {returnTypeFullName}) Update{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,")
.WriteLine($"{updateTypeFullName} {updateVarName},")
.WriteLine($"{keyTypeFullName} {keyVarName})")
.WriteOpeningBracket()
.WriteLine($"var {existingVarName} = dbContext.{propertyName}.FirstOrDefault(x => x.{keyPropertyName} == {keyVarName});")
.WriteLine($"if ({existingVarName} == null) return (false, null);")
.WriteLine($"var success = dbContext.SaveChanges() >= 0;")
.WriteLine($"return (success, new {returnTypeFullName}({existingVarName}));")
.WriteClosingBracket();
builder
.WriteLine();
}
return builder;
}
}
}

View File

@ -1,53 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MapTo.Sources;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace MapTo
{
internal enum EfMethodsAttributeType
{
Add,
Update,
Invalid
}
internal record CandidateMember(
MemberDeclarationSyntax MemberDeclarationSyntax,
EfMethodsAttributeType AttributeType
);
internal class EfMethodsSyntaxReceiver : ISyntaxReceiver
{
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
/// <inheritdoc />
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
{
return;
}
var attributeSyntax = attributes
.SelectMany(a => a.Attributes)
.FirstOrDefault(a => a.Name is
IdentifierNameSyntax { Identifier: { ValueText: EfGeneratorAttributeSource.AttributeName } }
or
QualifiedNameSyntax
{
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
Right: IdentifierNameSyntax { Identifier: { ValueText: EfGeneratorAttributeSource.AttributeName } }
}
);
if (attributeSyntax is not null)
{
CandidateTypes.Add(typeDeclarationSyntax);
}
}
}
}

View File

@ -50,7 +50,7 @@ namespace MapTo.Tests.Infrastructure
}
var driver = CSharpGeneratorDriver.Create(
new List<ISourceGenerator>() { new MapToGenerator(), new EfMethodsGenerator() },
new List<ISourceGenerator>() { new MapToGenerator()},
optionsProvider: new TestAnalyzerConfigOptionsProvider(analyzerConfigOptions),
parseOptions: new CSharpParseOptions(languageVersion)
);