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 _ignoredNamespaces; public ImmutableArray 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 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.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 GetEntityTypeSymbol(MemberDeclarationSyntax memberDeclarationSyntax, string attributeName, SemanticModel? semanticModel = null) { var attributeData = memberDeclarationSyntax.GetAttribute(attributeName); var sourceSymbol = GetEntityTypeSymbols(attributeData, semanticModel); return sourceSymbol; } protected ImmutableArray 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().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().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 GetEntityTypeSymbols(SyntaxNode? attributeSyntax, SemanticModel? semanticModel = null) { if (attributeSyntax is null) { return new ImmutableArray(){}; } semanticModel ??= Compilation.GetSemanticModel(attributeSyntax.SyntaxTree); var descendentNodes = attributeSyntax .DescendantNodes(); var sourceTypeExpressionSyntax = descendentNodes .OfType() .ToImmutableArray(); // cast var resultList = new List(); 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 GetEntityStringLiteralSymbol(SyntaxNode? attributeSyntax) { if (attributeSyntax is null) { return new ImmutableArray(){}; } var descendentNodes = attributeSyntax .DescendantNodes(); var sourceTypeExpressionSyntax = descendentNodes .OfType() .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() .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 GetAllowedMemberSyntaxes(string attributeName) { var classDeclarationSyntax = TypeSyntax; var syntaxes = classDeclarationSyntax.DescendantNodes() .OfType() .Where(x => x .DescendantNodes() .OfType().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 methodsModels = new List(); 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; } } }