Repair done

This commit is contained in:
CodeLiturgy 2022-09-05 01:42:08 +01:00
parent 4c4413da41
commit fa3dfc8e86
6 changed files with 128 additions and 170 deletions

View File

@ -21,53 +21,33 @@ namespace MapTo
internal class EfMethodsSyntaxReceiver : ISyntaxReceiver
{
public List<CandidateMember> CandidateMembers { get; } = new();
public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
/// <inheritdoc />
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is not MemberDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
if (syntaxNode is not TypeDeclarationSyntax { AttributeLists: { Count: >= 1 } attributes } typeDeclarationSyntax)
{
return;
}
var syntaxAttributeState = EfMethodsAttributeType.Invalid;
var attributeSyntax = attributes
.SelectMany(a => a.Attributes)
.FirstOrDefault(a =>
{
syntaxAttributeState = IsValidAttributeType(a);
return syntaxAttributeState != EfMethodsAttributeType.Invalid;
});
.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)
{
CandidateMembers.Add(new CandidateMember(typeDeclarationSyntax, syntaxAttributeState));
CandidateTypes.Add(typeDeclarationSyntax);
}
}
private static EfMethodsAttributeType IsValidAttributeType(AttributeSyntax a)
{
return a.Name switch
{
IdentifierNameSyntax { Identifier: { ValueText: EfAddMethodsAttributeSource.AttributeName } } // For: [EfAddMethods]
or QualifiedNameSyntax // For: [MapTo.EfAddMethods]
{
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
Right: IdentifierNameSyntax { Identifier: { ValueText: EfAddMethodsAttributeSource.AttributeName } }
} => EfMethodsAttributeType.Add,
IdentifierNameSyntax { Identifier: { ValueText: EfUpdateMethodsAttributeSource.AttributeName } } // For: [EfAddMethods]
or QualifiedNameSyntax // For: [MapTo.EfUpdateMethods]
{
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
Right: IdentifierNameSyntax { Identifier: { ValueText: EfUpdateMethodsAttributeSource.AttributeName } }
} => EfMethodsAttributeType.Update,
_ => EfMethodsAttributeType.Invalid
};
}
}
}

View File

@ -30,7 +30,7 @@ namespace MapTo
protected SourceGenerationOptions SourceGenerationOptions { get; }
protected MemberDeclarationSyntax MemberSyntax { get; }
protected TypeDeclarationSyntax TypeSyntax { get; }
protected ImmutableArray<string> Usings { get; private set; }
@ -44,14 +44,14 @@ namespace MapTo
protected EfGeneratorContext(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax memberSyntax)
TypeDeclarationSyntax typeSyntax)
{
Compilation = compilation;
_ignoredNamespaces = new();
Diagnostics = ImmutableArray<Diagnostic>.Empty;
Usings = ImmutableArray.Create("System", Constants.RootNamespace);
SourceGenerationOptions = sourceGenerationOptions;
MemberSyntax = memberSyntax;
TypeSyntax = typeSyntax;
EfUpdateMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfUpdateMethodsAttributeSource.FullyQualifiedName);
EfAddMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfAddMethodsAttributeSource.FullyQualifiedName);
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
@ -60,11 +60,11 @@ namespace MapTo
public static EfGeneratorContext Create(
Compilation compilation,
SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax typeSyntax)
TypeDeclarationSyntax typeSyntax)
{
EfGeneratorContext context = typeSyntax switch
{
PropertyDeclarationSyntax => new EfGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
ClassDeclarationSyntax => new EfGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
_ => throw new ArgumentOutOfRangeException()
};
@ -86,7 +86,7 @@ namespace MapTo
protected void AddUsingIfRequired(bool condition, string? ns)
{
if (ns is not null && condition && ns != MemberSyntax.GetNamespace() && !Usings.Contains(ns))
if (ns is not null && condition && ns != TypeSyntax.GetNamespace() && !Usings.Contains(ns))
{
Usings = Usings.Add(ns);
}
@ -238,13 +238,28 @@ namespace MapTo
namedTypeSymbolResult = null;
return false;
}
private ImmutableArray<MemberDeclarationSyntax> GetAllowedMemberSyntaxes(string attributeName)
{
ClassDeclarationSyntax classDeclarationSyntax = TypeSyntax as ClassDeclarationSyntax;
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(MemberSyntax.SyntaxTree);
var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
// get containing class type information
ClassDeclarationSyntax classDeclarationSyntax = MemberSyntax.Parent as ClassDeclarationSyntax;
ClassDeclarationSyntax classDeclarationSyntax = TypeSyntax as ClassDeclarationSyntax;
// context name
var dbContextName = classDeclarationSyntax.Identifier.ValueText;
@ -252,22 +267,11 @@ namespace MapTo
var contextNamespace = namespaceDeclaration.Name.ToString();
var contextTypeFullName = $"{contextNamespace}.{dbContextName}";
var addAttributeSymbols = classDeclarationSyntax.DescendantNodes()
.OfType<MemberDeclarationSyntax>()
.Where(x => x.DescendantNodes().OfType<AttributeSyntax>().Any(
syntax => syntax.Name.ToString() == EfUpdateMethodsAttributeSource.AttributeName
))
.ToImmutableArray();
var updateAttributesMembers = classDeclarationSyntax.DescendantNodes()
.OfType<MemberDeclarationSyntax>()
.Where(x => x.DescendantNodes().OfType<AttributeSyntax>().Any(
syntax => syntax.Name.ToString() == EfUpdateMethodsAttributeSource.AttributeName
))
.ToImmutableArray();
var addAttributeSymbols = GetAllowedMemberSyntaxes(EfAddMethodsAttributeSource.AttributeName);
var updateAttributesMembers = GetAllowedMemberSyntaxes(EfUpdateMethodsAttributeSource.AttributeName);
@ -275,12 +279,8 @@ namespace MapTo
foreach (var uProperty in updateAttributesMembers)
{
var entityTypeData = GetEntityTypeData(uProperty, semanticModel);
string entityIdentifierName = entityTypeData.Name;
string entityFullName = entityTypeData.ToDisplayString();
var propertyNamex = (uProperty as PropertyDeclarationSyntax ).Identifier.ValueText;
var entityDataModel = new EfEntityDataModel(propertyNamex, entityFullName, entityIdentifierName);
var newUpdateModel = ExtractEfUpdateMethodsModel(semanticModel, uProperty, entityDataModel);
var newUpdateModel = ExtractEfUpdateMethodsModel(semanticModel, uProperty);
methodsModels.Add(newUpdateModel);
}
@ -297,13 +297,7 @@ namespace MapTo
}
SpinWait.SpinUntil(() => Debugger.IsAttached);
var classType = semanticModel.GetTypeInfo(classDeclarationSyntax);
if (classType.Type != null)
{
var addAttributes = GetSymbolsFromAttribute(classType.Type, EfAddMethodsTypeSymbol);
}
//SpinWait.SpinUntil(() => Debugger.IsAttached);
return new EfMethodsModel(
SourceGenerationOptions,
@ -341,16 +335,22 @@ namespace MapTo
returnTypeIdentifierName);
}
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, MemberDeclarationSyntax memberDeclarationSyntax, EfEntityDataModel efEntityDataModel)
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, MemberDeclarationSyntax uProperty)
{
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyPropertyName = ExtractNameOfMemberName(memberDeclarationSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var entityTypeData = GetEntityTypeData(uProperty, semanticModel);
string entityIdentifierName = entityTypeData.Name;
string entityFullName = entityTypeData.ToDisplayString();
var propertyNamex = (uProperty as PropertyDeclarationSyntax ).Identifier.ValueText;
var entityDataModel = new EfEntityDataModel(propertyNamex, entityFullName, entityIdentifierName);
var keyMemberType = FindFirstTypeInfoOfMemberExpressionSyntax(semanticModel, MemberSyntax);
var efAddAttributeTypeSymbols =
GetEntityTypeSymbol(uProperty, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var secondTypeInfo = FindSecondTypeInfoOfMemberExpressionSyntax(semanticModel, MemberSyntax);
var keyPropertyName = ExtractNameOfMemberName(uProperty, EfUpdateMethodsAttributeSource.AttributeName, semanticModel);
var keyMemberType = FindFirstTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
var secondTypeInfo = FindSecondTypeInfoOfMemberExpressionSyntax(semanticModel, TypeSyntax);
@ -361,17 +361,17 @@ namespace MapTo
// Try grabbing string literal if there's no nameof in it
if (keyPropertyName == string.Empty)
{
keyPropertyName = GetEntityStringLiteralSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
keyPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()?
.Token.ValueText ?? "Id";
}
if (efAddAttributeTypeSymbols == null) return null;
string updateTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName;
string updateTypeFullName = efEntityDataModel.EntityTypeFullName;
string returnTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = efEntityDataModel.EntityTypeFullName;
string updateTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string updateTypeFullName = entityDataModel.EntityTypeFullName;
string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = entityDataModel.EntityTypeFullName;
if (efAddAttributeTypeSymbols.Length > 0)
{
@ -387,7 +387,7 @@ namespace MapTo
}
return new EfUpdateMethodsModel(efEntityDataModel, updateTypeFullName, updateTypeIdentifierName, returnTypeFullName,
return new EfUpdateMethodsModel(entityDataModel, updateTypeFullName, updateTypeIdentifierName, returnTypeFullName,
returnTypeIdentifierName, keyPropertyName, keyMemberFullName);
}
@ -419,7 +419,7 @@ namespace MapTo
return null;
}
var containingNamespace = MemberSyntax.GetNamespace();
var containingNamespace = TypeSyntax.GetNamespace();
var symbolNamespace = symbol.ContainingNamespace.ToDisplayString();
return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First())
? symbol.ToDisplayString()

View File

@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MapTo.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace MapTo.Sources.EfMethods
{
public static class EfGeneratorExtensionMethods
{
public static void InitializeEfGenerator(this ISourceGenerator sourceGenerator, GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new EfMethodsSyntaxReceiver());
}
/// <inheritdoc />
public static void ExecuteEfGenerator(this ISourceGenerator sourceGenerator, GeneratorExecutionContext context)
{
try
{
var options = SourceGenerationOptions.From(context);
var compilation = context.Compilation
.AddSource(ref context, EfAddMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfUpdateMethodsAttributeSource.Generate(options));
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateMembers.Any())
{
AddGeneratedExtensions(context, compilation, receiver.CandidateMembers, options);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
private static void AddGeneratedExtensions( GeneratorExecutionContext context, Compilation compilation, IEnumerable<CandidateMember> candidateMembers, SourceGenerationOptions options)
{
//SpinWait.SpinUntil(() => Debugger.IsAttached);
foreach (var candidateMember in candidateMembers)
{
string addSourceTemplate = context.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains("AddToEntityTemplate"))?
.GetText()?
.ToString() ?? string.Empty;
string updateSourceTemplate = context.AdditionalFiles
.FirstOrDefault(x => x.Path.Contains("UpdateEntityTemplate"))?
.GetText()?
.ToString() ?? string.Empty;
var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
if (mappingContext.Model is null)
{
continue;
}
var (source, hintName) = candidateMember.MemberDeclarationSyntax switch
{
PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate, updateSourceTemplate),
_ => throw new ArgumentOutOfRangeException()
};
context.AddSource(hintName, source);
}
}
}
}

View File

@ -0,0 +1,49 @@
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

@ -32,12 +32,13 @@ namespace MapTo
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, EfUpdateMethodsAttributeSource.Generate(options))
.AddSource(ref context, EfGetOneByAttributeSource.Generate(options));
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateMembers.Any())
if (context.SyntaxReceiver is EfMethodsSyntaxReceiver receiver && receiver.CandidateTypes.Any())
{
AddGeneratedExtensions(context, compilation, receiver.CandidateMembers, options);
AddGeneratedExtensions(context, compilation, receiver.CandidateTypes, options);
}
}
catch (Exception ex)
@ -47,7 +48,7 @@ namespace MapTo
}
}
private static void AddGeneratedExtensions(GeneratorExecutionContext context, Compilation compilation, IEnumerable<CandidateMember> candidateMembers, SourceGenerationOptions options)
private static void AddGeneratedExtensions(GeneratorExecutionContext context, Compilation compilation, IEnumerable<TypeDeclarationSyntax> candidateMembers, SourceGenerationOptions options)
{
foreach (var candidateMember in candidateMembers)
@ -63,7 +64,7 @@ namespace MapTo
.ToString() ?? string.Empty;
var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax);
var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
@ -72,9 +73,9 @@ namespace MapTo
continue;
}
var (source, hintName) = candidateMember.MemberDeclarationSyntax switch
var (source, hintName) = candidateMember switch
{
PropertyDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate, updateSourceTemplate),
ClassDeclarationSyntax => EfMethodsSource.Generate(mappingContext.Model, addSourceTemplate, updateSourceTemplate),
_ => throw new ArgumentOutOfRangeException()
};

View File

@ -66,8 +66,10 @@ namespace MapTo.Sources
entityTypeFullName,
propertyName);
}
builder.WriteLine();
}
builder
@ -78,13 +80,14 @@ namespace MapTo.Sources
.WriteClosingBracket();
var generatedCode = builder.ToString();
var hintName = $"{entityTypeName}Extensions.g.cs";
var hintName = $"{model.ContextTypeName}Extensions.g.cs";
return new(generatedCode, hintName);
}
private static SourceBuilder ParseTemplate(this SourceBuilder builder, string finalTemplate)
{
string[] array = finalTemplate.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach (var line in array)
{
@ -123,6 +126,7 @@ namespace MapTo.Sources
if (addSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
.WriteLine(GeneratedFilesHeader)
.WriteLine($"public static (bool, {model.ReturnTypeFullName}) Add{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,")
@ -172,6 +176,7 @@ namespace MapTo.Sources
if (updateSourceTemplate.IsEmpty())
{
builder
.WriteComment("Generated body")
.WriteLine($"public static (bool, {returnTypeFullName}) Update{entityTypeName}(")
.WriteLine($"this {contextFullName} dbContext,")
.WriteLine($"{updateTypeFullName} {updateVarName},")