AutoMapProperty/AutoMapProperty/AutoMapProperty.cs
2023-06-27 20:57:22 +02:00

933 lines
41 KiB
C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Text.RegularExpressions;
namespace AutoMapProperty
{
[Generator]
public class AutoMapPropertyGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValuesProvider<PropertyDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration)
.Where(m => m is not null)!;
IncrementalValueProvider<(Compilation, ImmutableArray<PropertyDeclarationSyntax>)> compilationAndClasses
= context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Item1, source.Item2, spc));
}
static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken ct)
=> node is PropertyDeclarationSyntax m && m.AttributeLists.Count > 0;
private const string AutoMapPropertyAttributeName = "AutoMapPropertyHelper.AutoMapPropertyAttribute";
static PropertyDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken ct)
{
// we know the node is a MethodDeclarationSyntax thanks to IsSyntaxTargetForGeneration
var propertyDeclarationSyntax = (PropertyDeclarationSyntax)context.Node;
// loop through all the attributes on the method
foreach (AttributeListSyntax attributeListSyntax in propertyDeclarationSyntax.AttributeLists)
{
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
{
if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
{
// weird, we couldn't get the symbol, ignore it
continue;
}
INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
string fullName = attributeContainingTypeSymbol.ToDisplayString();
// Is the attribute theAutoMapPropertyAttributeName attribute?
if (fullName == AutoMapPropertyAttributeName)
{
// return the parent class of the method
return propertyDeclarationSyntax;
}
}
}
// we didn't find the attribute we were looking for
return null;
}
public class ClassToGenerate
{
/// <summary>
/// Class or Interface
/// </summary>
public TypeDeclarationSyntax SourceContainer;
public List<string> Usings;
public string Namespace;
public string Name;
public string FullName => Namespace + "." + Name;
public record struct ProperyGenerationInfo(PropertyDeclarationSyntax prop, ITypeSymbol propTypeSymbol, bool customFrom, IMethodSymbol? customFromMethod, bool customTo, IMethodSymbol? customToMethod);
public List<ProperyGenerationInfo> Properties;
public struct MappableTypes
{
public ITypeSymbol FromType;
public ITypeSymbol ToType;
public ClassToGenerate OurTargetType;
public IPropertySymbol FromProperty;
public bool FromIsReadOnly => FromProperty.SetMethod == null;
public bool CustomFrom;
public IMethodSymbol? CustomFromMethod;
public bool CustomTo;
public IMethodSymbol? CustomToMethod;
public MappableTypes(ITypeSymbol from, ITypeSymbol to, IPropertySymbol fromProperty, bool customFrom, IMethodSymbol? customFromMethod, bool customTo, IMethodSymbol? customToMethod, ClassToGenerate? isOurType)
{
FromType = from;
ToType = to;
FromProperty = fromProperty;
CustomFrom = customFrom;
CustomFromMethod = customFromMethod;
CustomTo = customTo;
CustomToMethod = customToMethod;
OurTargetType = isOurType;
}
}
public Dictionary<string, MappableTypes> MappableProperties = new Dictionary<string, MappableTypes>();
public ClassToGenerate(TypeDeclarationSyntax sourceClass, string name, string namespaceName, List<string> usings, List<ProperyGenerationInfo> properties)
{
SourceContainer = sourceClass;
Namespace = namespaceName;
Name = name;
Properties = properties;
Usings = usings;
}
}
private static void Execute(Compilation compilation, ImmutableArray<PropertyDeclarationSyntax> properties, SourceProductionContext context)
{
if (properties.IsDefaultOrEmpty)
{
// nothing to do yet
return;
}
IEnumerable<PropertyDeclarationSyntax> distinctClasses = properties.Distinct();
List<ClassToGenerate> enumsToGenerate = GetTypesToGenerate(compilation, distinctClasses, context);
// If there were errors in the EnumDeclarationSyntax, we won't create an
// EnumToGenerate for it, so make sure we have something to generate
foreach (ClassToGenerate classToGenerate in enumsToGenerate)
{
//throw new Exception(classToGenerate.Name);
string result = GenerateExtensionClass(classToGenerate);
context.AddSource(classToGenerate.Name + ".g.cs", SourceText.From(result, Encoding.UTF8));
}
}
static List<ClassToGenerate> GetTypesToGenerate(Compilation compilation, IEnumerable<PropertyDeclarationSyntax> props, SourceProductionContext context)
{
// Create a list to hold our output
var classesToGenerate = new Dictionary<string, ClassToGenerate>();
// Get the semantic representation of our marker attribute
INamedTypeSymbol? propAttribute = compilation.GetTypeByMetadataName(AutoMapPropertyAttributeName);
if (propAttribute == null)
{
// If this is null, the compilation couldn't find the marker attribute type
// which suggests there's something very wrong! Bail out..
return classesToGenerate.Values.ToList();
}
foreach (PropertyDeclarationSyntax propSyntax in props)
{
// stop if we're asked to
context.CancellationToken.ThrowIfCancellationRequested();
// Get the semantic representation of the enum syntax
SemanticModel semanticModel = compilation.GetSemanticModel(propSyntax.Parent!.SyntaxTree);
//Get the parent class or interface
TypeDeclarationSyntax PropParentDecl = (TypeDeclarationSyntax)propSyntax.Parent!;
if (PropParentDecl is not ClassDeclarationSyntax && PropParentDecl is not InterfaceDeclarationSyntax)
continue;
if (semanticModel.GetDeclaredSymbol(PropParentDecl) is not INamedTypeSymbol classSymbol)
continue;
if (semanticModel.GetDeclaredSymbol(propSyntax) is not IPropertySymbol propSymbol)
continue;
var root = PropParentDecl.SyntaxTree.GetCompilationUnitRoot();
string className = classSymbol.Name;
foreach (AttributeData attr in propSymbol.GetAttributes())
{
if (!propAttribute.Equals(attr.AttributeClass, SymbolEqualityComparer.Default))
{
// This isn't the [EnumExtensions] attribute
continue;
}
//get the autogen name
string dataName = (string)attr.ConstructorArguments[0].Value!;
//the final name
string name = className + dataName;
if (!classesToGenerate.ContainsKey(name))
{
//Copy all usings
List<string> usings = root.Usings.Select(x => x.ToString()).ToList();
string ns = classSymbol.ContainingNamespace.ToString();
usings.Add("using " + ns + ";");
//Create the carrier type
classesToGenerate[name] = new ClassToGenerate(PropParentDecl, name, ns, usings, new List<ClassToGenerate.ProperyGenerationInfo>());
}
PropertyDeclarationSyntax? mem = propSyntax;
try
{
var newList = GetModifiedAttributeList(compilation, propSyntax.AttributeLists);
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ23", "Error", "New list on: {1} {0}", "NEJ", DiagnosticSeverity.Warning, true), null, string.Join(", ", newList.Select(x => x.ToString())), propSymbol.Name)
);
//Modify attributes to not include our generation attribute
mem = propSyntax.WithAttributeLists(newList);
}
catch (Exception ex)
{
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ23", "Error", "Error processing {0}", "NEJ", DiagnosticSeverity.Error, true), null, propSymbol.Name)
);
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ23", "Error", "{0}", "NEJ", DiagnosticSeverity.Error, true), null, ex.Message)
);
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ23", "Error", "{0}", "NEJ", DiagnosticSeverity.Error, true), null, ex.StackTrace)
);
}
var typeSymbol = propSymbol.Type;
//If we have a special overwrite type, replace it
if (attr.ConstructorArguments.Count() >= 2)
{
INamedTypeSymbol dataTypename = attr?.ConstructorArguments[1].Value as INamedTypeSymbol;
mem = mem.WithType(SyntaxFactory.ParseTypeName(dataTypename.ToDisplayString() + " "));
typeSymbol = dataTypename;
}
//if the property is readonly, remove the readonly modifier
if (mem.Modifiers.Any(x => x.IsKind(SyntaxKind.ReadOnlyKeyword)))
{
mem = mem.WithModifiers(SyntaxFactory.TokenList(mem.Modifiers.Where(x => !x.IsKind(SyntaxKind.ReadOnlyKeyword))));
}
//if property has non automatic(backing field) getter or setter, remove them
//add the default backing field getter and setter
mem = mem.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[]
{
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
})));
//if property has initializer or expression body, remove them
mem = mem.WithInitializer(null);
mem = mem.WithExpressionBody(null);
//if property is abstract remove the abstract modifier
if (mem.Modifiers.Any(x => x.IsKind(SyntaxKind.AbstractKeyword)))
{
mem = mem.WithModifiers(SyntaxFactory.TokenList(mem.Modifiers.Where(x => !x.IsKind(SyntaxKind.AbstractKeyword))));
}
//now the has trailing semicolon, which is wrong
mem = mem.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None));
//if we dont want to generate converter
bool customFrom = false;
IMethodSymbol? customFromMethod = null;
bool customTo = false;
IMethodSymbol? customToMethod = null;
if (attr.ConstructorArguments.Count() >= 3)
{
customTo = true;
customToMethod = ProcessCustomAttribute(context, classSymbol, attr.ConstructorArguments[2]);
}
if (attr.ConstructorArguments.Count() >= 4)
{
customFrom = true;
customFromMethod = ProcessCustomAttribute(context, classSymbol, attr.ConstructorArguments[3]);
}
//Add the property to the carrier type
classesToGenerate[name].Properties.Add(
new ClassToGenerate.ProperyGenerationInfo(mem, typeSymbol, customFrom, customFromMethod, customTo, customToMethod)
);
}
}
foreach (ClassToGenerate cls in classesToGenerate.Values)
{
ProcessClassToGenerateVariations(context, compilation, classesToGenerate, cls);
}
return classesToGenerate.Values.ToList();
}
public static IMethodSymbol? ProcessCustomAttribute(SourceProductionContext cont, INamedTypeSymbol classSymbol, TypedConstant attr)
{
var arg = attr.Value as string;
//get all methods of the declaring parent type
var methods = classSymbol.GetMembers().OfType<IMethodSymbol>();
//get the method with the same name as the attribute argument
var method = methods.FirstOrDefault(x => x.Name == arg);
if (method == null)
{
cont.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ10", "No method found", "Couldn't find method {0} on {1}", "NEJ", DiagnosticSeverity.Error, true), null, arg, classSymbol.Name)
);
return null;
}
//check if the method is static
if (!method.IsStatic)
{
cont.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor("NEJ10", "Method not static", "Method {0} on {1} is not static", "NEJ", DiagnosticSeverity.Error, true), null, arg, classSymbol.Name)
);
return null;
}
return method;
}
public record struct PropertyGenInfo(ITypeSymbol type, IPropertySymbol? symbol = null, bool customFrom = false, IMethodSymbol? customFromMethod = null, bool customTo = false, IMethodSymbol? customToMethod = null);
public static void ProcessClassToGenerateVariations(SourceProductionContext context, Compilation compilation, Dictionary<string, ClassToGenerate> classesToGenerate, ClassToGenerate cls)
{
var possibleCandidates = new List<(string, PropertyGenInfo)>();
//check if the newName already exists in compilation
var sym = compilation.GetTypesByMetadataName(cls.FullName).FirstOrDefault();
if (compilation.GetTypesByMetadataName(cls.FullName).FirstOrDefault() is INamedTypeSymbol symb)
{
possibleCandidates.AddRange(GetSymbolsForTypedSymbol(compilation, symb, classesToGenerate)
.Select(x => (x.Item1, x.Item2)));
}
else
{
possibleCandidates.AddRange(GetInfoFromClassToGenerate(compilation, cls));
}
//get properties on the source class
var comp = compilation.GetSemanticModel(cls.SourceContainer.SyntaxTree);
var symbol = comp.GetDeclaredSymbol(cls.SourceContainer)!;
var sourceCandidates = GetSymbolsForTypedSymbol(compilation, symbol, classesToGenerate);
//only add possible candidates that are also in sourceCanddidates
//check if there are multiples if so report a diagnostic
var source = sourceCandidates
.Where(x => !string.IsNullOrWhiteSpace(x.Item1))
//Remove entries with duplicate names
.GroupBy(x => x.Item1)
.Select(x => x.First());
var possible = possibleCandidates
.Where(x => !string.IsNullOrWhiteSpace(x.Item1))
//Remove entries with duplicate names
.GroupBy(x => x.Item1)
.Select(x => x.First());
var sourceNames = source.ToDictionary(x => x.Item1, x => x.Item2);
var possibleNames = possible.ToDictionary(x => x.Item1, x => x.Item2);
cls.MappableProperties = possibleNames.Keys.Where(x => sourceNames.Keys.Contains(x))
.ToDictionary(x => x, x =>
{
var concreteType = GetConcreteType(possibleNames[x].type);
var fullName = GetFullString(concreteType);
var ourType = classesToGenerate.FirstOrDefault(y => y.Value.FullName == fullName).Value;
//could not be resolved due to missing namespace, lets be optimistic
if (ourType == null && string.IsNullOrEmpty(concreteType.ContainingNamespace?.Name))
{
ourType = classesToGenerate.FirstOrDefault(y => y.Value.Name == fullName).Value;
}
/*
if (!isOurType && !fullName.StartsWith("System"))
{
Console.WriteLine(x + "'s type: " + fullName + " is not in our types");
Console.WriteLine(concreteType.ContainingNamespace.Name);
Console.WriteLine(concreteType.Name);
}*/
return new ClassToGenerate.MappableTypes(
sourceNames[x].type,
possibleNames[x].type,
sourceNames[x].symbol,
possibleNames[x].customFrom,
possibleNames[x].customFromMethod,
possibleNames[x].customTo,
possibleNames[x].customToMethod,
ourType);
});
//c.MappableProperties = c.MappableProperties.ToDictionary(x => x.Item1, x => x.Item2).Select(x => (x.Key, x.Value)).ToList();
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"NEJ01", "Report candidates vs real", "{0} had: {1} possible candidates and:{2} source values, but in the end there were: {3} values",
"NEJ", DiagnosticSeverity.Warning, true), null, cls.Name, possibleCandidates.Count, sourceCandidates.Count, cls.MappableProperties.Count));
}
static ITypeSymbol GetConcreteType(ITypeSymbol symb)
{
//if it's array, dicionary, etc. then we weant to check the underlying type
if (symb is IArrayTypeSymbol arr)
{
//Console.WriteLine(symb.GetFullMetadataName() + " is array: " + GetFullString(arr.ElementType));
return GetConcreteType(arr.ElementType);
}
//also check for ICollection and IEnumerable
if (symb.Name == "ICollection" || symb.Name == "IEnumerable")
{
//if it's generic, then we want to check the underlying type
if (symb is INamedTypeSymbol gen)
{
//Console.WriteLine(symb.GetFullMetadataName() + " is generic: " + GetFullString(gen.TypeArguments[0]));
return GetConcreteType(gen.TypeArguments[0]);
}
}
return symb;
}
static SymbolDisplayFormat displayFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
public static List<(string, PropertyGenInfo)> GetSymbolsForTypedSymbol(Compilation comp, INamedTypeSymbol cls, Dictionary<string, ClassToGenerate> ctg)
{
var result = new List<(string, PropertyGenInfo)>();
result.AddRange(cls.GetMembers().OfType<IPropertySymbol>().Select(x => (x.Name.Trim(), new PropertyGenInfo(x.Type, x))));
//add from to be generated symbols
result.AddRange(GetSymbolsFromCurrentGeneration(comp, cls.Name.Trim(), ctg));
//add parent symbols
if (cls.BaseType != null)
result.AddRange(GetSymbolsForTypedSymbol(comp, cls.BaseType, ctg));
return result;
}
public static List<(string, PropertyGenInfo)> GetSymbolsFromCurrentGeneration(Compilation comp, string className, Dictionary<string, ClassToGenerate> currentClassesToBeGenerated)
{
if (currentClassesToBeGenerated.ContainsKey(className))
{
var c = currentClassesToBeGenerated[className];
return GetInfoFromClassToGenerate(comp, c);
}
return new List<(string, PropertyGenInfo)>();
}
public static List<(string, PropertyGenInfo)> GetInfoFromClassToGenerate(Compilation comp, ClassToGenerate cls)
{
return cls.Properties.Select(x =>
{
var name = x.prop.Identifier.ToFullString().Trim();
return (name,
new PropertyGenInfo(x.propTypeSymbol, (IPropertySymbol?)null, x.customFrom, x.customFromMethod, x.customTo, x.customToMethod));
})
.ToList();
}
#region Attribute_Editing
public static SyntaxList<AttributeListSyntax> GetModifiedAttributeList(Compilation com, SyntaxList<AttributeListSyntax> attListList)
{
if (attListList == null)
return attListList;
if (attListList.Count <= 0)
return attListList;
var toRemove = new List<AttributeListSyntax>();
foreach (var node in attListList.ToList().Where(x => x != null && x.Attributes != null && x.Attributes.Count > 0))
{
var atts = GetModifiedAttributes(com, node.Attributes);
node.WithAttributes(atts);
if (atts.Count <= 0)
toRemove.Add(node);
}
return new SyntaxList<AttributeListSyntax>(attListList.Where(x => !toRemove.Contains(x)));
}
public static SeparatedSyntaxList<AttributeSyntax> GetModifiedAttributes(Compilation com, SeparatedSyntaxList<AttributeSyntax> attList)
{
var toRemove = new List<AttributeSyntax>();
foreach (var x in attList.ToList().Where(x => x != null).Where(x => IsOurAttribute(com, x)))
{
toRemove.Add(x);
}
var newList = SyntaxFactory.SeparatedList<AttributeSyntax>(attList.Where(x => !toRemove.Contains(x)));
return newList;
}
public static bool IsOurAttribute(Compilation com, AttributeSyntax syntax)
{
if (com.GetSemanticModel(syntax.SyntaxTree).GetSymbolInfo(syntax).Symbol is not IMethodSymbol attrSymbol)
return syntax.ToString().Contains(AutoMapPropertyAttributeName);
var t = com.GetTypeByMetadataName(AutoMapPropertyAttributeName);
if (t == null)
throw new Exception("AutoMapPropertyAttributeName is null");
return attrSymbol.ContainingSymbol.Equals(t, SymbolEqualityComparer.Default);
}
#endregion
public static string GetFullMethodName(IMethodSymbol method)
{
var sb = new StringBuilder();
sb.Append(method.ContainingType.ToDisplayString()).Append(".").Append(method.Name);
return sb.ToString();
}
public static string GeneratePropertyFrom(string Key, ITypeSymbol FromType, ITypeSymbol ToType, bool nullable = true)
{
var sb = new StringBuilder();
sb.Append(Key).Append(@" = ");
var concreteToType = GetConcreteType(ToType);
var concreteFromType = GetConcreteType(FromType);
bool isArray = !SymbolEqualityComparer.Default.Equals(concreteToType, ToType);
if (isArray)
{
sb.Append("(").Append(ToType).Append(@")(source.").Append(Key).Append(" != null ? ");
sb.Append("source.").Append(Key);
sb.Append(nullable ? "?" : "");
if (!SymbolEqualityComparer.Default.Equals(concreteToType, concreteFromType))
{
sb.Append(@".Select(x => (").Append(GetFullString(concreteToType)).Append(@")x)");
sb.Append(nullable ? "?" : "");
}
//Add the appropriate ToHashSet or ToSet or ToCollection or ToList
if (ToType.Name.Contains("Set"))
sb.Append(@".ToHashSet()");
else
sb.Append(@".ToList()");
sb.Append(" : new ");
if (ToType.Name.Contains("Set"))
sb.Append("HashSet<").Append(GetFullString(concreteToType)).Append(">()");
else
sb.Append("List<").Append(GetFullString(concreteToType)).Append(">()");
sb.Append(")");
}
else
{
sb.Append("(").Append(ToType).Append(@")source.").Append(Key);
}
return sb.ToString();
}
public static string GetFullString(ITypeSymbol symbol)
{
return symbol.ToDisplayString(format: displayFormat);
}
public static string? GenerateCorrectFromProperty(string Key, ClassToGenerate.MappableTypes Value, bool nullable = true)
{
if (!Value.CustomFrom)
{
return GeneratePropertyFrom(Key, Value.FromType, Value.ToType, nullable);
}
else
{
if (Value.CustomFromMethod == null)
return null;
StringBuilder sb = new StringBuilder();
sb.Append(Key).Append(" = (").Append(GetFullString(Value.ToType)).Append(@")").Append(GetFullMethodName(Value.CustomFromMethod)).Append(@"(providers, this, source.").Append(Key).Append(")");
return sb.ToString();
}
}
public static string GeneratePropertyTo(string Key, ITypeSymbol Type)
{
var sb = new StringBuilder();
sb.Append(Key).Append(@" = (").Append(GetFullString(Type)).Append(@")this.").Append(Key);
return sb.ToString();
}
public static string? GenerateCorrectToProperty(string Key, ClassToGenerate.MappableTypes Value)
{
var retSb = new StringBuilder();
retSb.Append("source.");
if (!Value.CustomTo)
{
if (Value.FromIsReadOnly)
return null;
retSb.Append(GeneratePropertyTo(Key, Value.FromType));
}
else
{
if (Value.CustomToMethod == null)
return null;
//if it's readonly we are probably just mutating some other data based of of this one
if (Value.FromIsReadOnly)
{
var callerSb = new StringBuilder();
callerSb.Append(GetFullMethodName(Value.CustomToMethod)).Append(@"(providers, source, this.").Append(Key).Append(");");
return callerSb.ToString();
}
StringBuilder sb = new StringBuilder();
sb.Append(Key).Append(" = (").Append(GetFullString(Value.FromType)).Append(@")").Append(GetFullMethodName(Value.CustomToMethod)).Append(@"(providers, source, this.").Append(Key).Append(")");
retSb.Append(sb.ToString());
}
retSb.Append(";");
return retSb.ToString();
}
public static string? GenerateEqualityOperator(string name, ClassToGenerate.MappableTypes Value, string? prefix = null)
{
var prefixedName = prefix == null ? name : prefix + "." + name;
//if the type is also being generated we actually want to use the generated EqualityExpression
if (Value.OurTargetType != null)
{
var concrecteType = GetConcreteType(Value.ToType);
//cannot process collections/arrays
if (SymbolEqualityComparer.Default.Equals(concrecteType, Value.ToType))
{
var childSb = new StringBuilder();
foreach (var prop in Value.OurTargetType.MappableProperties)
{
var op = GenerateEqualityOperator(prop.Key, prop.Value, prefixedName);
if (string.IsNullOrEmpty(op))
{
continue;
}
childSb.Append(@"
").Append(op).Append(" && ");
}
childSb.Append(@"true");
return childSb.ToString();
}
else
return null;
}
if (!SymbolEqualityComparer.Default.Equals(Value.FromType, Value.ToType))
{
return null;
}
var sb = new StringBuilder();
sb.Append("item1.").Append(prefixedName).Append(" == item2.").Append(prefixedName);
return sb.ToString();
}
public static string GenerateExtensionClass(ClassToGenerate classToGenerate)
{
var sb = new StringBuilder();
foreach (var usi in classToGenerate.Usings)
{
sb.AppendLine(usi);
}
sb.Append(@"
#nullable enable
#pragma warning disable CS8600
#pragma warning disable CS8601
#pragma warning disable CS8602
#pragma warning disable CS8603
#pragma warning disable CS8604
#pragma warning disable CS8618
#pragma warning disable CS8625
#pragma warning disable CS0109
#pragma warning disable CS8767
#pragma warning disable CS0659
namespace ").Append(classToGenerate.Namespace).Append(@"
{
[System.CodeDom.Compiler.GeneratedCode(""AutoMapProperty"", ""1.0.0"")]
public partial ").Append(classToGenerate.SourceContainer is ClassDeclarationSyntax ? "class " : "interface ").Append(classToGenerate.Name);
if (classToGenerate.SourceContainer is ClassDeclarationSyntax && !classToGenerate.SourceContainer.Modifiers.Any(x => x.IsKind(SyntaxKind.AbstractKeyword)))
{
sb.Append(": IAutomappedAttribute<").Append(classToGenerate.SourceContainer.Identifier).Append(",").Append(classToGenerate.Name).Append(">");
sb.Append(", System.Collections.Generic.IEqualityComparer<").Append(classToGenerate.Name).Append(">");
}
sb.Append(@"
{");
foreach (var prop in classToGenerate.Properties)
{
sb.Append(@"
").Append(prop.prop.ToString());
}
if (classToGenerate.SourceContainer is ClassDeclarationSyntax cds)
{
sb.Append(@"
public static implicit operator ").Append(classToGenerate.Name).Append(@"(").Append(classToGenerate.SourceContainer.Identifier).Append(@" source)
{
var dat = new ").Append(classToGenerate.Name).Append(@"();
return dat.ApplyFrom(null, source);
}");
sb.Append(@"
public new ").Append(classToGenerate.Name).Append(@" ApplyFrom(System.IServiceProvider? providers, ").Append(classToGenerate.SourceContainer.Identifier).Append(@" source)
{
if(source == null)
return null;
{");
foreach (var prop in classToGenerate.MappableProperties)
{
var property = GenerateCorrectFromProperty(prop.Key, prop.Value);
if (property == null)
continue;
sb.Append(@"
").Append(property).Append(@";");
}
sb.Append(@"
}
return this;
}");
sb.Append(@"
public new System.Linq.Expressions.Expression<Func<").Append(classToGenerate.SourceContainer.Identifier).Append(", ").Append(classToGenerate.Name).Append(@">> GetProjectorFrom(System.IServiceProvider providers) => (source)=>new ").Append(classToGenerate.Name).Append(@"()
{");
foreach (var prop in classToGenerate.MappableProperties)
{
var property = GenerateCorrectFromProperty(prop.Key, prop.Value, false);
if (property == null)
continue;
sb.Append(@"
").Append(property).Append(@",");
}
sb.Append(@"
};");
sb.Append(@"
public static new System.Linq.Expressions.Expression<Func<").Append(classToGenerate.Name).Append(@", bool>> EqualExpression(System.IServiceProvider providers,").Append(classToGenerate.SourceContainer.Identifier).Append(@" item1) => (item2)=>
(");
foreach (var prop in classToGenerate.MappableProperties)
{
var op = GenerateEqualityOperator(prop.Key, prop.Value);
if (string.IsNullOrEmpty(op))
{
continue;
}
sb.Append(@"
").Append(op).Append(" && ");
}
sb.Append(@"true
);");
sb.Append(@"
public static new System.Linq.Expressions.Expression<Func<").Append(classToGenerate.SourceContainer.Identifier).Append(@", bool>> EqualExpression(System.IServiceProvider providers,").Append(classToGenerate.Name).Append(@" item1) => (item2)=>
(");
foreach (var prop in classToGenerate.MappableProperties)
{
var op = GenerateEqualityOperator(prop.Key, prop.Value);
if (string.IsNullOrEmpty(op))
{
continue;
}
sb.Append(@"
").Append(op).Append(" && ");
}
sb.Append(@"true
);");
sb.Append(@"
public override bool Equals(object? obj){
if(obj == null)
return false;
if (ReferenceEquals(obj, this))
return true;
").Append(classToGenerate.Name).Append(@" item2 = obj as ").Append(classToGenerate.Name).Append(@";
var item1 = this;
return (");
foreach (var prop in classToGenerate.MappableProperties)
{
var op = GenerateEqualityOperator(prop.Key, prop.Value);
if (string.IsNullOrEmpty(op))
{
continue;
}
sb.Append(@"
").Append(op).Append(" && ");
}
sb.Append(@"true
);
}");
sb.Append(@"
public bool Equals(").Append(classToGenerate.Name).Append(" item1, ").Append(classToGenerate.Name).Append(@" item2){
if (item1 is null || item2 is null)
{
return false;
}
if (ReferenceEquals(item1, item2))
return true;
return (");
foreach (var prop in classToGenerate.MappableProperties)
{
var op = GenerateEqualityOperator(prop.Key, prop.Value);
if (string.IsNullOrEmpty(op))
{
continue;
}
sb.Append(@"
").Append(op).Append(" && ");
}
sb.Append(@"true
);
}");
sb.Append(@"
public int GetHashCode(").Append(classToGenerate.Name).Append(@" obj)
{
return obj.GetHashCode();
}");
var abstr = cds.Modifiers.Any(x => x.IsKind(SyntaxKind.AbstractKeyword));
if (!abstr)
{
sb.Append(@"
public new ").Append(classToGenerate.SourceContainer.Identifier).Append(@" ApplyTo(System.IServiceProvider? providers, ").Append(classToGenerate.SourceContainer.Identifier).Append(@" source)
{
if(source == null)
return null;
{");
foreach (var prop in classToGenerate.MappableProperties)
{
var property = GenerateCorrectToProperty(prop.Key, prop.Value);
if (property == null)
continue;
sb.Append(@"
").Append(property);
}
sb.Append(@"
}
return source;
}");
sb.Append(@"
public static explicit operator ").Append(classToGenerate.SourceContainer.Identifier).Append(@"(").Append(classToGenerate.Name).Append(@" source)
{
var dat = new ").Append(classToGenerate.SourceContainer.Identifier).Append(@"();
return source.ApplyTo(null, dat);
}");
}
}
sb.Append(@"
}
}
#pragma warning restore CS8600
#pragma warning restore CS8601
#pragma warning restore CS8602
#pragma warning restore CS8603
#pragma warning restore CS8604
#pragma warning restore CS8618
#pragma warning restore CS8625
#pragma warning restore CS0109
#pragma warning restore CS8767
#pragma warning restore CS0659
#nullable disable
");
return sb.ToString();
}
}
public static class Utils
{
public static string GetFullMetadataName(this ISymbol s)
{
if (s == null || IsRootNamespace(s))
{
return string.Empty;
}
var sb = new StringBuilder(s.MetadataName);
var last = s;
s = s.ContainingSymbol;
while (!IsRootNamespace(s))
{
if (s is ITypeSymbol && last is ITypeSymbol)
{
sb.Insert(0, '+');
}
else
{
sb.Insert(0, '.');
}
sb.Insert(0, s.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
//sb.Insert(0, s.MetadataName);
s = s.ContainingSymbol;
}
return sb.ToString();
}
private static bool IsRootNamespace(ISymbol symbol)
{
INamespaceSymbol s = null;
return ((s = symbol as INamespaceSymbol) != null) && s.IsGlobalNamespace;
}
}
}