BlueWest.EfGenerator/src/BlueWest.MapTo/EfMethods/EfGeneratorContext.cs

495 lines
21 KiB
C#
Raw Normal View History

2022-09-06 00:47:58 +03:00
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;
}
}
}