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 classDeclarations = context.SyntaxProvider .CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration) .Where(m => m is not null)!; IncrementalValueProvider<(Compilation, ImmutableArray)> 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 { /// /// Class or Interface /// public TypeDeclarationSyntax SourceContainer; public List 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 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 MappableProperties = new Dictionary(); public ClassToGenerate(TypeDeclarationSyntax sourceClass, string name, string namespaceName, List usings, List properties) { SourceContainer = sourceClass; Namespace = namespaceName; Name = name; Properties = properties; Usings = usings; } } private static void Execute(Compilation compilation, ImmutableArray properties, SourceProductionContext context) { if (properties.IsDefaultOrEmpty) { // nothing to do yet return; } IEnumerable distinctClasses = properties.Distinct(); List 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 GetTypesToGenerate(Compilation compilation, IEnumerable props, SourceProductionContext context) { // Create a list to hold our output var classesToGenerate = new Dictionary(); // 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 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()); } 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(); //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 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 ctg) { var result = new List<(string, PropertyGenInfo)>(); result.AddRange(cls.GetMembers().OfType().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 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 GetModifiedAttributeList(Compilation com, SyntaxList attListList) { if (attListList == null) return attListList; if (attListList.Count <= 0) return attListList; var toRemove = new List(); 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(attListList.Where(x => !toRemove.Contains(x))); } public static SeparatedSyntaxList GetModifiedAttributes(Compilation com, SeparatedSyntaxList attList) { var toRemove = new List(); foreach (var x in attList.ToList().Where(x => x != null).Where(x => IsOurAttribute(com, x))) { toRemove.Add(x); } var newList = SyntaxFactory.SeparatedList(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> 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> 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> 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; } } }