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 internal class EfMethodsSyntaxReceiver : ISyntaxReceiver
{ {
public List<CandidateMember> CandidateMembers { get; } = new(); public List<TypeDeclarationSyntax> CandidateTypes { get; } = new();
/// <inheritdoc />
public void OnVisitSyntaxNode(SyntaxNode syntaxNode) 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; return;
} }
var syntaxAttributeState = EfMethodsAttributeType.Invalid;
var attributeSyntax = attributes var attributeSyntax = attributes
.SelectMany(a => a.Attributes) .SelectMany(a => a.Attributes)
.FirstOrDefault(a => .FirstOrDefault(a => a.Name is
{ IdentifierNameSyntax { Identifier: { ValueText: EfGeneratorAttributeSource.AttributeName } }
syntaxAttributeState = IsValidAttributeType(a); or
return syntaxAttributeState != EfMethodsAttributeType.Invalid; QualifiedNameSyntax
}); {
Left: IdentifierNameSyntax { Identifier: { ValueText: Constants.RootNamespace } },
Right: IdentifierNameSyntax { Identifier: { ValueText: EfGeneratorAttributeSource.AttributeName } }
}
);
if (attributeSyntax is not null) 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 SourceGenerationOptions SourceGenerationOptions { get; }
protected MemberDeclarationSyntax MemberSyntax { get; } protected TypeDeclarationSyntax TypeSyntax { get; }
protected ImmutableArray<string> Usings { get; private set; } protected ImmutableArray<string> Usings { get; private set; }
@ -44,14 +44,14 @@ namespace MapTo
protected EfGeneratorContext( protected EfGeneratorContext(
Compilation compilation, Compilation compilation,
SourceGenerationOptions sourceGenerationOptions, SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax memberSyntax) TypeDeclarationSyntax typeSyntax)
{ {
Compilation = compilation; Compilation = compilation;
_ignoredNamespaces = new(); _ignoredNamespaces = new();
Diagnostics = ImmutableArray<Diagnostic>.Empty; Diagnostics = ImmutableArray<Diagnostic>.Empty;
Usings = ImmutableArray.Create("System", Constants.RootNamespace); Usings = ImmutableArray.Create("System", Constants.RootNamespace);
SourceGenerationOptions = sourceGenerationOptions; SourceGenerationOptions = sourceGenerationOptions;
MemberSyntax = memberSyntax; TypeSyntax = typeSyntax;
EfUpdateMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfUpdateMethodsAttributeSource.FullyQualifiedName); EfUpdateMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfUpdateMethodsAttributeSource.FullyQualifiedName);
EfAddMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfAddMethodsAttributeSource.FullyQualifiedName); EfAddMethodsTypeSymbol = compilation.GetTypeByMetadataNameOrThrow(EfAddMethodsAttributeSource.FullyQualifiedName);
AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis"); AddUsingIfRequired(sourceGenerationOptions.SupportNullableStaticAnalysis, "System.Diagnostics.CodeAnalysis");
@ -60,11 +60,11 @@ namespace MapTo
public static EfGeneratorContext Create( public static EfGeneratorContext Create(
Compilation compilation, Compilation compilation,
SourceGenerationOptions sourceGenerationOptions, SourceGenerationOptions sourceGenerationOptions,
MemberDeclarationSyntax typeSyntax) TypeDeclarationSyntax typeSyntax)
{ {
EfGeneratorContext context = typeSyntax switch EfGeneratorContext context = typeSyntax switch
{ {
PropertyDeclarationSyntax => new EfGeneratorContext(compilation, sourceGenerationOptions, typeSyntax), ClassDeclarationSyntax => new EfGeneratorContext(compilation, sourceGenerationOptions, typeSyntax),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@ -86,7 +86,7 @@ namespace MapTo
protected void AddUsingIfRequired(bool condition, string? ns) 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); Usings = Usings.Add(ns);
} }
@ -238,13 +238,28 @@ namespace MapTo
namedTypeSymbolResult = null; namedTypeSymbolResult = null;
return false; 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() private EfMethodsModel? CreateMappingModel()
{ {
var semanticModel = Compilation.GetSemanticModel(MemberSyntax.SyntaxTree); var semanticModel = Compilation.GetSemanticModel(TypeSyntax.SyntaxTree);
// get containing class type information // get containing class type information
ClassDeclarationSyntax classDeclarationSyntax = MemberSyntax.Parent as ClassDeclarationSyntax; ClassDeclarationSyntax classDeclarationSyntax = TypeSyntax as ClassDeclarationSyntax;
// context name // context name
var dbContextName = classDeclarationSyntax.Identifier.ValueText; var dbContextName = classDeclarationSyntax.Identifier.ValueText;
@ -252,22 +267,11 @@ namespace MapTo
var contextNamespace = namespaceDeclaration.Name.ToString(); var contextNamespace = namespaceDeclaration.Name.ToString();
var contextTypeFullName = $"{contextNamespace}.{dbContextName}"; 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() var addAttributeSymbols = GetAllowedMemberSyntaxes(EfAddMethodsAttributeSource.AttributeName);
.OfType<MemberDeclarationSyntax>()
.Where(x => x.DescendantNodes().OfType<AttributeSyntax>().Any( var updateAttributesMembers = GetAllowedMemberSyntaxes(EfUpdateMethodsAttributeSource.AttributeName);
syntax => syntax.Name.ToString() == EfUpdateMethodsAttributeSource.AttributeName
))
.ToImmutableArray();
@ -275,12 +279,8 @@ namespace MapTo
foreach (var uProperty in updateAttributesMembers) foreach (var uProperty in updateAttributesMembers)
{ {
var entityTypeData = GetEntityTypeData(uProperty, semanticModel);
string entityIdentifierName = entityTypeData.Name; var newUpdateModel = ExtractEfUpdateMethodsModel(semanticModel, uProperty);
string entityFullName = entityTypeData.ToDisplayString();
var propertyNamex = (uProperty as PropertyDeclarationSyntax ).Identifier.ValueText;
var entityDataModel = new EfEntityDataModel(propertyNamex, entityFullName, entityIdentifierName);
var newUpdateModel = ExtractEfUpdateMethodsModel(semanticModel, uProperty, entityDataModel);
methodsModels.Add(newUpdateModel); methodsModels.Add(newUpdateModel);
} }
@ -297,13 +297,7 @@ namespace MapTo
} }
SpinWait.SpinUntil(() => Debugger.IsAttached); //SpinWait.SpinUntil(() => Debugger.IsAttached);
var classType = semanticModel.GetTypeInfo(classDeclarationSyntax);
if (classType.Type != null)
{
var addAttributes = GetSymbolsFromAttribute(classType.Type, EfAddMethodsTypeSymbol);
}
return new EfMethodsModel( return new EfMethodsModel(
SourceGenerationOptions, SourceGenerationOptions,
@ -341,16 +335,22 @@ namespace MapTo
returnTypeIdentifierName); returnTypeIdentifierName);
} }
private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, MemberDeclarationSyntax memberDeclarationSyntax, EfEntityDataModel efEntityDataModel) private EfUpdateMethodsModel ExtractEfUpdateMethodsModel(SemanticModel semanticModel, MemberDeclarationSyntax uProperty)
{ {
var efAddAttributeTypeSymbols = var entityTypeData = GetEntityTypeData(uProperty, semanticModel);
GetEntityTypeSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel); string entityIdentifierName = entityTypeData.Name;
string entityFullName = entityTypeData.ToDisplayString();
var keyPropertyName = ExtractNameOfMemberName(memberDeclarationSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel); 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 // Try grabbing string literal if there's no nameof in it
if (keyPropertyName == string.Empty) if (keyPropertyName == string.Empty)
{ {
keyPropertyName = GetEntityStringLiteralSymbol(MemberSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel) keyPropertyName = GetEntityStringLiteralSymbol(TypeSyntax, EfUpdateMethodsAttributeSource.AttributeName, semanticModel)
.FirstOrDefault()? .FirstOrDefault()?
.Token.ValueText ?? "Id"; .Token.ValueText ?? "Id";
} }
if (efAddAttributeTypeSymbols == null) return null; if (efAddAttributeTypeSymbols == null) return null;
string updateTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName; string updateTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string updateTypeFullName = efEntityDataModel.EntityTypeFullName; string updateTypeFullName = entityDataModel.EntityTypeFullName;
string returnTypeIdentifierName = efEntityDataModel.EntityTypeIdentifierName; string returnTypeIdentifierName = entityDataModel.EntityTypeIdentifierName;
string returnTypeFullName = efEntityDataModel.EntityTypeFullName; string returnTypeFullName = entityDataModel.EntityTypeFullName;
if (efAddAttributeTypeSymbols.Length > 0) 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); returnTypeIdentifierName, keyPropertyName, keyMemberFullName);
} }
@ -419,7 +419,7 @@ namespace MapTo
return null; return null;
} }
var containingNamespace = MemberSyntax.GetNamespace(); var containingNamespace = TypeSyntax.GetNamespace();
var symbolNamespace = symbol.ContainingNamespace.ToDisplayString(); var symbolNamespace = symbol.ContainingNamespace.ToDisplayString();
return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First()) return containingNamespace != symbolNamespace && _ignoredNamespaces.Contains(symbol.ContainingNamespace.ToDisplayParts().First())
? symbol.ToDisplayString() ? 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 var compilation = context.Compilation
.AddSource(ref context, EfGeneratorAttributeSource.Generate(options)) .AddSource(ref context, EfGeneratorAttributeSource.Generate(options))
.AddSource(ref context, EfAddMethodsAttributeSource.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) 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) foreach (var candidateMember in candidateMembers)
@ -63,7 +64,7 @@ namespace MapTo
.ToString() ?? string.Empty; .ToString() ?? string.Empty;
var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember.MemberDeclarationSyntax); var mappingContext = EfGeneratorContext.Create(compilation, options, candidateMember);
mappingContext.Diagnostics.ForEach(context.ReportDiagnostic); mappingContext.Diagnostics.ForEach(context.ReportDiagnostic);
@ -72,9 +73,9 @@ namespace MapTo
continue; 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() _ => throw new ArgumentOutOfRangeException()
}; };

View File

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