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

615 lines
27 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using BlueWest.EfMethods.EfMethods.AttributeSources;
using BlueWest.EfMethods.Extensions;
using BlueWest.EfMethods.Sources;
using BlueWest.Sources;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSharpExtensions = Microsoft.CodeAnalysis.CSharpExtensions;
#pragma warning disable CS8602
namespace BlueWest.EfMethods
{
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? FindTypeInfoOfMemberExpressionSyntax(SemanticModel semanticModel, SyntaxNode attributeSyntax, int skip = 0)
{
semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree);
var descendentNodes = attributeSyntax
.DescendantNodes();
// Get the type of the key member id
var expression = descendentNodes
.OfType<MemberAccessExpressionSyntax>()
.Skip(skip)
.FirstOrDefault();
if (expression != null)
{
return semanticModel.GetTypeInfo(expression);
}
return null;
}
protected string ExtractNameOfMemberName(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null, int skip = 0)
{
var attributeData = memberDeclarationSyntax.GetAttribute(attributeName);
var sourceSymbol = ExtractNameOfMemberName(attributeData, semanticModel, skip);
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, int skip)
{
if (attributeSyntax is null)
{
return string.Empty;
}
var descendentNodes = attributeSyntax
.DescendantNodes();
var idNode = descendentNodes.OfType<InvocationExpressionSyntax>()
.Skip(skip)
.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))
{
// Todo add diagnostic
//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 addMembers = GetAllowedMemberSyntaxes(EfAddMethodsAttributeSource.AttributeName);
var updateMembers = GetAllowedMemberSyntaxes(EfUpdateMethodsAttributeSource.AttributeName);
var getOneByMembers = GetAllowedMemberSyntaxes(EfGetOneByAttributeSource.AttributeName);
var getOneMembers = GetAllowedMemberSyntaxes(EfGetOneAttributeSource.AttributeName);
var efGetManyMembers = GetAllowedMemberSyntaxes(EfGetManyAttributeSource.AttributeName);
var getListMembers = GetAllowedMemberSyntaxes(EfGetListAttributeSource.AttributeName);
var getAddToListMembers = GetAllowedMemberSyntaxes(EfAddToListAttributeSource.AttributeName);
List<EfEntityDataModel> methodsModels = new List<EfEntityDataModel>();
foreach (var uProperty in getOneByMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetEntityByModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in addMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newAddModel = ExtractEfAddMethodsModel(semanticModel, uProperty, entityDataModel);
methodsModels.Add(newAddModel);
}
foreach (var uProperty in updateMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfUpdateMethodsModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in getOneMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetEntityWithModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in efGetManyMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newUpdateModel = ExtractEfGetManyModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newUpdateModel);
}
foreach (var uProperty in getListMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newModel = ExtractEfGetListModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newModel);
}
foreach (var uProperty in getAddToListMembers)
{
var entityDataModel = GetEntityData(uProperty, semanticModel);
var newModel = ExtractEfAddToListMethodsModel(uProperty, semanticModel, entityDataModel);
methodsModels.Add(newModel);
}
//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 = FindTypeInfoOfMemberExpressionSyntax(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 = FindTypeInfoOfMemberExpressionSyntax(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 EfGetListModel ExtractEfGetListModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
var efTypeofSymbols = GetEntityTypeSymbol(uProperty, EfGetListAttributeSource.AttributeName, semanticModel);
var listPropertyName = ExtractNameOfMemberName(uProperty, EfGetListAttributeSource.AttributeName, semanticModel);
var keyPropertyName = ExtractNameOfMemberName(uProperty, EfGetListAttributeSource.AttributeName, semanticModel, 1);;
var keyMemberType = FindTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
var keyFullTypeName = keyMemberType.Value.Type.ToDisplayString();
// Try grabbing string literal if there's no nameof in it
if (keyPropertyName == string.Empty)
{
listPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfGetListAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
}
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 EfGetListModel(entityDataModel, listPropertyName, keyPropertyName, keyFullTypeName, returnTypeIdentifierName, returnTypeFullName);
}
private EfAddToListModel ExtractEfAddToListMethodsModel(MemberDeclarationSyntax uProperty, SemanticModel semanticModel, EfEntityDataModel entityDataModel)
{
//SpinWait.SpinUntil(() => Debugger.IsAttached);
var efTypeofSymbols = GetEntityTypeSymbol(uProperty, EfAddToListAttributeSource.AttributeName, semanticModel);
var listPropertyName = ExtractNameOfMemberName(uProperty, EfAddToListAttributeSource.AttributeName, semanticModel);
var keyPropertyName = ExtractNameOfMemberName(uProperty, EfAddToListAttributeSource.AttributeName, semanticModel, 1);;
var keyMemberType = FindTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
var keyFullTypeName = keyMemberType.Value.Type.ToDisplayString();
var listEntityTypeInfo = (FindTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax, 2).Value.Type as INamedTypeSymbol).TypeArguments.FirstOrDefault();
var listEntityIdentifierName = listEntityTypeInfo.Name;
var listEntityFullTypeName = listEntityTypeInfo.ToDisplayString();
// Try grabbing string literal if there's no nameof in it
if (keyPropertyName == string.Empty)
{
listPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfAddToListAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
}
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
var createTypeIdentifierName =listEntityIdentifierName;
var createTypeFullName = listEntityFullTypeName;
// Grab create type from attribute argument
if (efTypeofSymbols.Length > 0)
{
createTypeIdentifierName = efTypeofSymbols[0].Name;
createTypeFullName = efTypeofSymbols[0].ToDisplayString();
}
// Grab return type from attribute argument
if (efTypeofSymbols.Length > 1)
{
returnTypeIdentifierName = efTypeofSymbols[1].Name;
returnTypeFullName = efTypeofSymbols[1].ToDisplayString();
}
return new EfAddToListModel(
entityDataModel,
listPropertyName,
listEntityIdentifierName,
listEntityFullTypeName,
keyPropertyName,
keyFullTypeName,
createTypeIdentifierName,
createTypeFullName,
returnTypeIdentifierName,
returnTypeFullName);
}
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;
}
}
}