From a0c9c95b6fc4a4547973d11f330c766c60def555 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 24 Jan 2026 18:16:10 +0100 Subject: [PATCH 1/2] better example --- TenJames.CompMap.sln | 69 ++++------- .../TenJames.CompMap.Example/Entitities.cs | 112 ------------------ .../TenJames.CompMap.Example/Program.cs | 24 ---- .../TenJames.CompMap/MapperGenerator.cs | 18 ++- .../{Properties => }/MappingOptions.cs | 78 +++++++----- .../Properties/launchSettings.json | 4 +- .../Example.Console/Example.Console.csproj | 6 +- example/Example.Console/Program.cs | 38 ++++++ example/Example.DTOS/DocumentDto.cs | 15 +++ example/Example.DTOS/Example.DTOS.csproj | 16 +++ example/Example.DTOS/UserDto.cs | 29 +++++ example/Example.Entities/Document.cs | 9 ++ .../Example.Entities/Example.Entities.csproj | 9 ++ example/Example.Entities/User.cs | 10 ++ 14 files changed, 217 insertions(+), 220 deletions(-) delete mode 100644 TenJames.CompMap/TenJames.CompMap.Example/Entitities.cs delete mode 100644 TenJames.CompMap/TenJames.CompMap.Example/Program.cs rename TenJames.CompMap/TenJames.CompMap/{Properties => }/MappingOptions.cs (61%) rename TenJames.CompMap/TenJames.CompMap.Example/TenJames.CompMap.Example.csproj => example/Example.Console/Example.Console.csproj (57%) create mode 100644 example/Example.Console/Program.cs create mode 100644 example/Example.DTOS/DocumentDto.cs create mode 100644 example/Example.DTOS/Example.DTOS.csproj create mode 100644 example/Example.DTOS/UserDto.cs create mode 100644 example/Example.Entities/Document.cs create mode 100644 example/Example.Entities/Example.Entities.csproj create mode 100644 example/Example.Entities/User.cs diff --git a/TenJames.CompMap.sln b/TenJames.CompMap.sln index d58c3bc..d5fb080 100644 --- a/TenJames.CompMap.sln +++ b/TenJames.CompMap.sln @@ -1,73 +1,56 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenJames.CompMap", "TenJames.CompMap\TenJames.CompMap\TenJames.CompMap.csproj", "{9B224A43-D90A-4D47-8A92-1E02A9BA5658}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenJames.CompMap.Tests", "TenJames.CompMap\TenJames.CompMap.Tests\TenJames.CompMap.Tests.csproj", "{045DCA29-C2DC-4707-9521-98097B1B2F84}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenJames.CompMap.Example", "TenJames.CompMap\TenJames.CompMap.Example\TenJames.CompMap.Example.csproj", "{B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenJames.CompMap.IntegrationTests", "TenJames.CompMap\TenJames.CompMap.IntegrationTests\TenJames.CompMap.IntegrationTests.csproj", "{8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Entities", "example\Example.Entities\Example.Entities.csproj", "{11111111-1111-1111-1111-111111111111}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.DTOS", "example\Example.DTOS\Example.DTOS.csproj", "{22222222-2222-2222-2222-222222222222}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Console", "example\Example.Console\Example.Console.csproj", "{33333333-3333-3333-3333-333333333333}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|x64.ActiveCfg = Debug|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|x64.Build.0 = Debug|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|x86.ActiveCfg = Debug|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Debug|x86.Build.0 = Debug|Any CPU {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|Any CPU.Build.0 = Release|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|x64.ActiveCfg = Release|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|x64.Build.0 = Release|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|x86.ActiveCfg = Release|Any CPU - {9B224A43-D90A-4D47-8A92-1E02A9BA5658}.Release|x86.Build.0 = Release|Any CPU {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|Any CPU.Build.0 = Debug|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|x64.ActiveCfg = Debug|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|x64.Build.0 = Debug|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|x86.ActiveCfg = Debug|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Debug|x86.Build.0 = Debug|Any CPU {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|Any CPU.ActiveCfg = Release|Any CPU {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|Any CPU.Build.0 = Release|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|x64.ActiveCfg = Release|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|x64.Build.0 = Release|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|x86.ActiveCfg = Release|Any CPU - {045DCA29-C2DC-4707-9521-98097B1B2F84}.Release|x86.Build.0 = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|x64.ActiveCfg = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|x64.Build.0 = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|x86.ActiveCfg = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Debug|x86.Build.0 = Debug|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|Any CPU.Build.0 = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|x64.ActiveCfg = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|x64.Build.0 = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|x86.ActiveCfg = Release|Any CPU - {B7BB7EF5-EBA5-45F2-903F-9A0DBCAD0D9C}.Release|x86.Build.0 = Release|Any CPU {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|x64.ActiveCfg = Debug|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|x64.Build.0 = Debug|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|x86.ActiveCfg = Debug|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Debug|x86.Build.0 = Debug|Any CPU {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|Any CPU.Build.0 = Release|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|x64.ActiveCfg = Release|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|x64.Build.0 = Release|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|x86.ActiveCfg = Release|Any CPU - {8CB48D1B-7F2C-40F5-830D-0BC8AE46B07E}.Release|x86.Build.0 = Release|Any CPU + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22222222-2222-2222-2222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU + {33333333-3333-3333-3333-333333333333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33333333-3333-3333-3333-333333333333}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33333333-3333-3333-3333-333333333333}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33333333-3333-3333-3333-333333333333}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {11111111-1111-1111-1111-111111111111} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {22222222-2222-2222-2222-222222222222} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {33333333-3333-3333-3333-333333333333} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + EndGlobalSection EndGlobal diff --git a/TenJames.CompMap/TenJames.CompMap.Example/Entitities.cs b/TenJames.CompMap/TenJames.CompMap.Example/Entitities.cs deleted file mode 100644 index 2eefd6c..0000000 --- a/TenJames.CompMap/TenJames.CompMap.Example/Entitities.cs +++ /dev/null @@ -1,112 +0,0 @@ -//using TenJames.CompMap.Attributes; - -using TenJames.CompMap.Attributes; -using TenJames.CompMap.Mappper; - -namespace TenJames.CompMap.Example; - - -public class User { - public int Id { get; set; } - public string Name { get; set; } - public Guid Guid { get; set; } - public ICollection Documents { get; set; } -} - - -public class Document { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } -} - - -[MapFrom(typeof(User))] -public partial class UserReadDto { - public int Id { get; set; } - public string Name { get; set; } - public Guid Guid { get; set; } - public required string Title { get; set; } - public ICollection Documents { get; set; } - - private static partial UserUnmappedProperties GetUserUnmappedProperties(IMapper mapper, User source) - { - return new UserUnmappedProperties() - { - Title = source.Name + "'s Title", - }; - } -} - -[MapTo(typeof(User))] -public partial class UserCreateDto { - public string Name { get; set; } - public Guid Guid { get; set; } - - private static partial UserUnmappedProperties GetUserUnmappedProperties(IMapper mapper, UserCreateDto source) - { - return new UserUnmappedProperties() - { - - }; - } -} - -[MapFrom(typeof(Document))] -public partial class DocumentDto { - public string Title { get; set; } - public string Content { get; set; } -} - -/* - *Generated files - * - * UserReadDto.g.cs - * ```cs - * - * - using System; - using System.Collections.Generic; - - using TenJames.CompMap.Mappper; - - namespace TenJames.CompMap.Example; - - partial class UserReadDto - { - - /// - /// The following properties were not mapped because they do not exist in the target class - /// - internal class UserUnmappedProperties - { - /// - /// Found at /Entitities.cs at 29 - /// - public required string Title { get; set; } - } - - private static partial UserUnmappedProperties GetUserUnmappedProperties(IMapper mapper, User source); - - /// - /// Mapping method generated by TenJames.CompMap - /// - public static UserReadDto MapFrom(IMapper mapper, User source) - { - // Note: Some properties were not mapped due to missing counterparts in the target class. - var unmapped = GetUserUnmappedProperties(mapper, source); - return new UserReadDto - { - Id = source.Id, - Name = source.Name, - Guid = source.Guid, - Documents = mapper.Map >(source.Documents), - Title = unmapped.Title, - }; - } - } - * ``` - * - * Others follow similar pattern - * - */ diff --git a/TenJames.CompMap/TenJames.CompMap.Example/Program.cs b/TenJames.CompMap/TenJames.CompMap.Example/Program.cs deleted file mode 100644 index 0351545..0000000 --- a/TenJames.CompMap/TenJames.CompMap.Example/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ - -using System.Text.Json; -using TenJames.CompMap.Example; -using TenJames.CompMap.Mappper; -var userCreate = new UserCreateDto() -{ - Name = "John Doe", - Guid = Guid.NewGuid() -}; - - -var mapper = new BaseMapper(); -var options = new JsonSerializerOptions() -{ - WriteIndented = true -}; -Console.WriteLine("--- User Create ---"); -Console.WriteLine(JsonSerializer.Serialize(userCreate, options)); -var u = mapper.Map(userCreate); -Console.WriteLine("--- User ---"); -Console.WriteLine(JsonSerializer.Serialize(u, options)); -var userDto = mapper.Map(u); -Console.WriteLine("--- UserReadDto ---"); -Console.WriteLine(JsonSerializer.Serialize(userDto, options)); \ No newline at end of file diff --git a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs index 81c266f..2e52960 100644 --- a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs +++ b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs @@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -using TenJames.CompMap.Properties; namespace TenJames.CompMap; @@ -23,7 +22,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Generate the source code. context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()), - ((ctx, t) => GenerateCode(ctx, t.Left, t.Right))); + ((ctx, t) => GenerateCode(ctx, t.Left, t.Right!))); } private static MappingOptions? GetClassDeclarationForSourceGen( @@ -74,15 +73,14 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati // Get all properties including inherited ones var allSourceProperties = MappingOptions.GetAllProperties(ma.SemanticModel, ma.ClassDeclarationSyntax); - // Get semantic model for target (which might be in a different file) - var targetSemanticModel = compilation.GetSemanticModel(ma.Target.SyntaxTree); - var allTargetProperties = MappingOptions.GetAllProperties(targetSemanticModel, ma.Target); + // Get target properties from the symbol (works for both same-compilation and external assemblies) + var allTargetProperties = MappingOptions.GetAllPropertiesFromSymbol(ma.TargetSymbol); var matchingFields = allSourceProperties .Where(prop => allTargetProperties .Any(targetProp => targetProp.Name == prop.Name)) .ToList(); - + if (ma.AttributeName.Contains("MapFrom")) { @@ -115,7 +113,7 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati } sourceText.AppendLine(); { - + sourceText.AppendLine("/// "); sourceText.AppendLine("/// Mapping method generated by TenJames.CompMap"); sourceText.AppendLine("/// "); @@ -126,7 +124,7 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati sourceText.AppendLine("// Note: Some properties were not mapped due to missing counterparts in the target class."); sourceText.AppendLine($"var unmapped = Get{ma.TargetName}UnmappedProperties(mapper, source);"); } - + sourceText.AppendLine($"return new {className}"); sourceText.AppendLine("{"); sourceText.IncreaseIndent(); @@ -152,7 +150,7 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati } sourceText.DecreaseIndent(); sourceText.AppendLine("};"); - + } } else if (ma.AttributeName.Contains("MapTo")) @@ -232,4 +230,4 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati context.AddSource($"{className}.g.cs", SourceText.From(sourceText.ToString(), Encoding.UTF8)); } } -} \ No newline at end of file +} diff --git a/TenJames.CompMap/TenJames.CompMap/Properties/MappingOptions.cs b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs similarity index 61% rename from TenJames.CompMap/TenJames.CompMap/Properties/MappingOptions.cs rename to TenJames.CompMap/TenJames.CompMap/MappingOptions.cs index a01ea30..313819f 100644 --- a/TenJames.CompMap/TenJames.CompMap/Properties/MappingOptions.cs +++ b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs @@ -1,31 +1,43 @@ +namespace TenJames.CompMap; + using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace TenJames.CompMap.Properties; - public class MappingOptions { public ClassDeclarationSyntax ClassDeclarationSyntax { get; set; } public string ClassName => ClassDeclarationSyntax.Identifier.Text; public string AttributeName { get; set; } public string Namespace { get; set; } - public ClassDeclarationSyntax Target { get; set; } - public string TargetName => Target.Identifier.Text; + + // Target can be either from source (same compilation) or from metadata (external assembly) + public ClassDeclarationSyntax? TargetSyntax { get; set; } + public INamedTypeSymbol TargetSymbol { get; set; } + + public string TargetName => TargetSymbol.Name; public string TargetNamespace { get; set; } - public string TargetFullName => string.IsNullOrEmpty(TargetNamespace) ? TargetName : $"{TargetNamespace}.{TargetName}"; + public string TargetFullName => string.IsNullOrEmpty(TargetNamespace) || TargetNamespace == "GlobalNamespace" + ? TargetName + : $"{TargetNamespace}.{TargetName}"; public SemanticModel SemanticModel { get; set; } /// - /// Gets all properties including inherited ones from a class declaration + /// Gets all properties including inherited ones from a class declaration (when source is available) /// public static List GetAllProperties(SemanticModel semanticModel, ClassDeclarationSyntax classDecl) { - var properties = new List(); var symbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol; + return symbol == null ? new List() : GetAllPropertiesFromSymbol(symbol); + } - if (symbol == null) return properties; + /// + /// Gets all properties including inherited ones from a type symbol (works for external assemblies) + /// + public static List GetAllPropertiesFromSymbol(INamedTypeSymbol symbol) + { + var properties = new List(); // Walk up the inheritance chain to get all properties var currentType = symbol; @@ -61,40 +73,52 @@ public static List GetAllProperties(SemanticModel semanticModel, C { var ns = classDeclarationSyntax.FirstAncestorOrSelf(); var fileScoped = classDeclarationSyntax.FirstAncestorOrSelf(); - + var namespaceName = ns != null ? ns.Name.ToString() : fileScoped != null ? fileScoped.Name.ToString() : "GlobalNamespace"; - + foreach (var attributeSyntax in classDeclarationSyntax.AttributeLists.SelectMany(attributeListSyntax => attributeListSyntax.Attributes)) { var attributeName = attributeSyntax.Name.ToString(); if (AttributeDefinitions.GetAllAttributes().Select(x => x.Name).Any(x => attributeName.Contains(x))) { - var targetClass = attributeSyntax.ArgumentList?.Arguments.First().Expression switch { - TypeOfExpressionSyntax typeOfExpression => context.SemanticModel.GetSymbolInfo(typeOfExpression.Type).Symbol - ?.DeclaringSyntaxReferences.First().GetSyntax() as ClassDeclarationSyntax, - _ => null - } ?? throw new InvalidOperationException("Target type could not be determined."); - - // Get target namespace - var targetNs = targetClass.FirstAncestorOrSelf(); - var targetFileScoped = targetClass.FirstAncestorOrSelf(); - - var targetNamespace = targetNs != null - ? targetNs.Name.ToString() - : targetFileScoped != null - ? targetFileScoped.Name.ToString() - : "GlobalNamespace"; + // Get the target type symbol + INamedTypeSymbol? targetSymbol = null; + ClassDeclarationSyntax? targetSyntax = null; + + if (attributeSyntax.ArgumentList?.Arguments.First().Expression is TypeOfExpressionSyntax typeOfExpression) + { + var symbolInfo = context.SemanticModel.GetSymbolInfo(typeOfExpression.Type); + targetSymbol = symbolInfo.Symbol as INamedTypeSymbol; + + // Try to get syntax if available (same compilation) + if (targetSymbol != null) + { + var syntaxRef = targetSymbol.DeclaringSyntaxReferences.FirstOrDefault(); + targetSyntax = syntaxRef?.GetSyntax() as ClassDeclarationSyntax; + } + } + + if (targetSymbol == null) + { + throw new InvalidOperationException("Target type could not be determined."); + } + + // Get target namespace from the symbol + var targetNamespace = targetSymbol.ContainingNamespace.IsGlobalNamespace + ? "GlobalNamespace" + : targetSymbol.ContainingNamespace.ToDisplayString(); return new MappingOptions { ClassDeclarationSyntax = classDeclarationSyntax, AttributeName = attributeName, Namespace = namespaceName, - Target = targetClass, + TargetSyntax = targetSyntax, + TargetSymbol = targetSymbol, TargetNamespace = targetNamespace, SemanticModel = context.SemanticModel }; @@ -110,4 +134,4 @@ public class PropertyInfo public string Name { get; set; } = string.Empty; public string TypeFullName { get; set; } = string.Empty; public IPropertySymbol PropertySymbol { get; set; } = null!; -} \ No newline at end of file +} diff --git a/TenJames.CompMap/TenJames.CompMap/Properties/launchSettings.json b/TenJames.CompMap/TenJames.CompMap/Properties/launchSettings.json index 418e777..bc41d22 100644 --- a/TenJames.CompMap/TenJames.CompMap/Properties/launchSettings.json +++ b/TenJames.CompMap/TenJames.CompMap/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "DebugRoslynSourceGenerator": { "commandName": "DebugRoslynComponent", - "targetProject": "../../TenJames.CompMap.Example/TenJames.CompMap.Example.csproj" + "targetProject": "../TenJames.CompMap.Example/TenJames.CompMap.Example.csproj" } } -} \ No newline at end of file +} diff --git a/TenJames.CompMap/TenJames.CompMap.Example/TenJames.CompMap.Example.csproj b/example/Example.Console/Example.Console.csproj similarity index 57% rename from TenJames.CompMap/TenJames.CompMap.Example/TenJames.CompMap.Example.csproj rename to example/Example.Console/Example.Console.csproj index 3a0a05b..6c44356 100644 --- a/TenJames.CompMap/TenJames.CompMap.Example/TenJames.CompMap.Example.csproj +++ b/example/Example.Console/Example.Console.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,8 @@ - + + + diff --git a/example/Example.Console/Program.cs b/example/Example.Console/Program.cs new file mode 100644 index 0000000..0b0b46e --- /dev/null +++ b/example/Example.Console/Program.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using Example.DTOS; +using Example.Entities; +using TenJames.CompMap.Mappper; + +var mapper = new BaseMapper(); +var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; + +Console.WriteLine("=== TenJames.CompMap Multi-Assembly Example ===\n"); + +// Create a user entity (simulating data from a database) +var user = new User +{ + Id = 1, + Name = "John Doe", + Email = "john.doe@example.com", + CreatedAt = DateTime.UtcNow, + Documents = new List + { + new() { Id = 1, Title = "First Document", Content = "Content of first document", CreatedAt = DateTime.UtcNow }, + new() { Id = 2, Title = "Second Document", Content = "Content of second document", CreatedAt = DateTime.UtcNow } + } +}; + +Console.WriteLine("1. User entity (from Example.Entities assembly):"); +Console.WriteLine(JsonSerializer.Serialize(user, jsonOptions)); + +// Map to DTO (demonstrates MapFrom across assemblies) +var userDto = mapper.Map(user); + +Console.WriteLine("\n2. UserReadDto (from Example.DTOS assembly):"); +Console.WriteLine(JsonSerializer.Serialize(userDto, jsonOptions)); + +Console.WriteLine("\n=== Cross-assembly mapping works! ==="); +Console.WriteLine("- Models are defined in Example.Entities assembly"); +Console.WriteLine("- DTOs with [MapFrom] are defined in Example.DTOS assembly"); +Console.WriteLine("- Source generator correctly generates mappings across assembly boundaries"); +Console.WriteLine("- Nested collections (Documents -> DocumentReadDto) are also mapped"); diff --git a/example/Example.DTOS/DocumentDto.cs b/example/Example.DTOS/DocumentDto.cs new file mode 100644 index 0000000..7c644bd --- /dev/null +++ b/example/Example.DTOS/DocumentDto.cs @@ -0,0 +1,15 @@ +using Example.Entities; +using TenJames.CompMap.Attributes; + +namespace Example.DTOS; + +/// +/// DTO for reading document data - maps FROM the Document entity +/// +[MapFrom(typeof(Document))] +public partial class DocumentReadDto +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; +} diff --git a/example/Example.DTOS/Example.DTOS.csproj b/example/Example.DTOS/Example.DTOS.csproj new file mode 100644 index 0000000..d6e62e4 --- /dev/null +++ b/example/Example.DTOS/Example.DTOS.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + enable + enable + + true + + + + + + + + diff --git a/example/Example.DTOS/UserDto.cs b/example/Example.DTOS/UserDto.cs new file mode 100644 index 0000000..bd4e91e --- /dev/null +++ b/example/Example.DTOS/UserDto.cs @@ -0,0 +1,29 @@ +using Example.Entities; +using TenJames.CompMap.Attributes; +using TenJames.CompMap.Mappper; + +namespace Example.DTOS; + +/// +/// DTO for reading user data - maps FROM the User entity +/// +[MapFrom(typeof(User))] +public partial class UserReadDto +{ + public int Id { get; set; } + public string Login { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public ICollection Documents { get; set; } = new List(); + + // Implementation required for unmapped properties + private static partial UserUnmappedProperties GetUserUnmappedProperties(IMapper mapper, User source) + { + return new UserUnmappedProperties + { + // Login doesn't exist on User, so we derive it from Email + Login = source.Email.Split('@')[0] + }; + } +} diff --git a/example/Example.Entities/Document.cs b/example/Example.Entities/Document.cs new file mode 100644 index 0000000..952c335 --- /dev/null +++ b/example/Example.Entities/Document.cs @@ -0,0 +1,9 @@ +namespace Example.Entities; + +public class Document +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } +} diff --git a/example/Example.Entities/Example.Entities.csproj b/example/Example.Entities/Example.Entities.csproj new file mode 100644 index 0000000..8a91831 --- /dev/null +++ b/example/Example.Entities/Example.Entities.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/example/Example.Entities/User.cs b/example/Example.Entities/User.cs new file mode 100644 index 0000000..7fc14f8 --- /dev/null +++ b/example/Example.Entities/User.cs @@ -0,0 +1,10 @@ +namespace Example.Entities; + +public class User +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public ICollection Documents { get; set; } = new List(); +} From b37aa6fd51eb780c05e5c67c1f4651873552e922 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 24 Jan 2026 18:32:08 +0100 Subject: [PATCH 2/2] adding records --- Readme.md | 6 +- .../MappingIntegrationTests.cs | 92 +++++++++++++++++++ .../TestDtos.cs | 57 ++++++++++++ .../TestEntities.cs | 35 +++++++ .../TenJames.CompMap/AttributeGenerator.cs | 2 +- .../TenJames.CompMap/MapperGenerator.cs | 23 ++--- .../TenJames.CompMap/MappingOptions.cs | 31 ++++--- 7 files changed, 218 insertions(+), 28 deletions(-) diff --git a/Readme.md b/Readme.md index 67d9ea0..e780821 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # CompileMapper (TenJames.CompMap) -> CompMap is a C# Roslyn-based source generator that automatically creates mapping methods between classes. It simplifies the process of converting one class type to another by generating the necessary code at compile time. +> CompMap is a C# Roslyn-based source generator that automatically creates mapping methods between classes or records. It simplifies the process of converting one class type to another by generating the necessary code at compile time. Ensure your DTO's are correctly mapped with compile time safety, to ensure valid changes are tracked in your project. @@ -108,7 +108,7 @@ The package will automatically be configured as a source generator. If you need /> ``` -Note: The `OutputItemType="Analyzer"` and `ReferenceOutputAssembly="false"` attributes are typically not required when using `dotnet add package`, as the package is already configured correctly. +Note: The `OutputItemType="Analyzer"` and `ReferenceOutputAssembly="false"` attributes are typically not required when using `dotnet add package`. ### Component registration @@ -133,7 +133,7 @@ Add Attributes to your DTO classes and then enjoy the generated mapping methods ## Example -See the [Example](./TenJames.CompMap/TenJames.CompMap.Example) project for a complete working example. +See the [Example](./example/Example.Console) project for a complete working example. ## Contributing diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs index ad8b3bd..8e1b42a 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs @@ -465,4 +465,96 @@ public void VehicleCreateDto_MapTo_ShouldMapPropertiesFromBaseClass() // Assert - Unmapped property Assert.False(vehicle.IsDeleted); } + + [Fact] + public void ContactReadDto_MapFrom_ShouldMapRecordProperties() + { + // Arrange + var contact = new Contact + { + Id = 1, + FirstName = "Alice", + LastName = "Smith", + Email = "alice.smith@example.com", + Phone = "+1234567890" + }; + + // Act + var dto = ContactReadDto.MapFrom(_mapper, contact); + + // Assert - Matching properties + Assert.Equal(contact.Id, dto.Id); + Assert.Equal(contact.FirstName, dto.FirstName); + Assert.Equal(contact.LastName, dto.LastName); + Assert.Equal(contact.Email, dto.Email); + Assert.Equal(contact.Phone, dto.Phone); + + // Assert - Unmapped property (computed) + Assert.Equal("Alice Smith", dto.FullName); + } + + [Fact] + public void AddressReadDto_MapFrom_ShouldMapAllRecordProperties() + { + // Arrange + var address = new Address + { + Id = 42, + Street = "123 Main St", + City = "New York", + PostalCode = "10001", + Country = "USA" + }; + + // Act + var dto = AddressReadDto.MapFrom(_mapper, address); + + // Assert + Assert.Equal(address.Id, dto.Id); + Assert.Equal(address.Street, dto.Street); + Assert.Equal(address.City, dto.City); + Assert.Equal(address.PostalCode, dto.PostalCode); + Assert.Equal(address.Country, dto.Country); + } + + [Fact] + public void NoteCreateDto_MapTo_ShouldCreateNoteRecord() + { + // Arrange + var dto = new NoteCreateDto + { + Title = "Meeting Notes", + Content = "Important discussion about Q1 goals" + }; + + // Act + var note = _mapper.Map(dto); + + // Assert - Matching properties + Assert.Equal(dto.Title, note.Title); + Assert.Equal(dto.Content, note.Content); + + // Assert - Unmapped properties (auto-generated) + Assert.Equal(0, note.Id); + Assert.NotEqual(default(DateTime), note.CreatedAt); + } + + [Fact] + public void Record_MapFrom_ClassEntity_ShouldWork() + { + // This tests mapping from a class entity to a record DTO + var contact = new Contact + { + Id = 5, + FirstName = "Bob", + LastName = "Jones", + Email = "bob@example.com", + Phone = "+9876543210" + }; + + var dto = _mapper.Map(contact); + + Assert.Equal(contact.Id, dto.Id); + Assert.Equal("Bob Jones", dto.FullName); + } } diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs index 4b03a25..1c4ef6e 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs @@ -215,3 +215,60 @@ private static partial VehicleUnmappedProperties GetVehicleUnmappedProperties(IM } } + +/// +/// Record DTO for reading contact data (testing MapFrom with records) +/// Has 1 unmapped property: FullName +/// +[MapFrom(typeof(Contact))] +public partial record ContactReadDto +{ + public int Id { get; init; } + public string FirstName { get; init; } = string.Empty; + public string LastName { get; init; } = string.Empty; + public string Email { get; init; } = string.Empty; + public string Phone { get; init; } = string.Empty; + + // Unmapped property (computed) + public required string FullName { get; init; } + + private static partial ContactUnmappedProperties GetContactUnmappedProperties(IMapper mapper, Contact source) + { + return new ContactUnmappedProperties + { + FullName = $"{source.FirstName} {source.LastName}" + }; + } +} + +/// +/// Record DTO for reading address data (testing simple MapFrom with records, no unmapped properties) +/// +[MapFrom(typeof(Address))] +public partial record AddressReadDto +{ + public int Id { get; init; } + public string Street { get; init; } = string.Empty; + public string City { get; init; } = string.Empty; + public string PostalCode { get; init; } = string.Empty; + public string Country { get; init; } = string.Empty; +} + +/// +/// Record DTO for creating notes (testing MapTo with records) +/// +[MapTo(typeof(Note))] +public partial record NoteCreateDto +{ + public string Title { get; init; } = string.Empty; + public string Content { get; init; } = string.Empty; + + private static partial NoteUnmappedProperties GetNoteUnmappedProperties(IMapper mapper, NoteCreateDto source) + { + return new NoteUnmappedProperties + { + Id = 0, + CreatedAt = DateTime.UtcNow + }; + } +} diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestEntities.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestEntities.cs index 9144f0f..3984614 100644 --- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestEntities.cs +++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestEntities.cs @@ -106,3 +106,38 @@ public abstract class BaseDto public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } + +/// +/// Record entity representing a contact (for testing record support) +/// +public record Contact +{ + public int Id { get; init; } + public string FirstName { get; init; } = string.Empty; + public string LastName { get; init; } = string.Empty; + public string Email { get; init; } = string.Empty; + public string Phone { get; init; } = string.Empty; +} + +/// +/// Record entity representing an address +/// +public record Address +{ + public int Id { get; init; } + public string Street { get; init; } = string.Empty; + public string City { get; init; } = string.Empty; + public string PostalCode { get; init; } = string.Empty; + public string Country { get; init; } = string.Empty; +} + +/// +/// Record entity for testing MapTo direction +/// +public record Note +{ + public int Id { get; init; } + public string Title { get; init; } = string.Empty; + public string Content { get; init; } = string.Empty; + public DateTime CreatedAt { get; init; } +} diff --git a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs b/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs index 229a96b..160a89c 100644 --- a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs +++ b/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs @@ -192,7 +192,7 @@ private static SourceText GenerateAttributeSourceText(AttributeDefinition attrib src.AppendLine($" /// "); src.AppendLine($" /// {attribute.Description}"); src.AppendLine($" /// "); - src.AppendLine($" [AttributeUsage(AttributeTargets.Class)]"); + src.AppendLine($" [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]"); src.AppendLine($" public class {attribute.Name}Attribute ("); foreach (var arg in attribute.Arguments) { diff --git a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs index 2e52960..3512fd1 100644 --- a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs +++ b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs @@ -16,8 +16,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { var provider = context.SyntaxProvider .CreateSyntaxProvider( - (s, _) => s is ClassDeclarationSyntax, - (ctx, _) => GetClassDeclarationForSourceGen(ctx)) + (s, _) => s is ClassDeclarationSyntax or RecordDeclarationSyntax, + (ctx, _) => GetTypeDeclarationForSourceGen(ctx)) .Where(t => t is not null); // Generate the source code. @@ -25,13 +25,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ((ctx, t) => GenerateCode(ctx, t.Left, t.Right!))); } - private static MappingOptions? GetClassDeclarationForSourceGen( + private static MappingOptions? GetTypeDeclarationForSourceGen( GeneratorSyntaxContext context) { - var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; + var typeDeclarationSyntax = (TypeDeclarationSyntax)context.Node; - // Go through all attributes of the class. - foreach (var attributeSyntax in classDeclarationSyntax.AttributeLists.SelectMany(attributeListSyntax => attributeListSyntax.Attributes)) + // Go through all attributes of the type. + foreach (var attributeSyntax in typeDeclarationSyntax.AttributeLists.SelectMany(attributeListSyntax => attributeListSyntax.Attributes)) { if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) continue; // if we can't get the symbol, ignore it @@ -39,7 +39,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var attributeName = attributeSymbol.ContainingType.ToDisplayString(); if (AttributeDefinitions.GetAllAttributes().Select(x => x.Name).Any(x => attributeName.Contains(x))) - return MappingOptions.Create(context, classDeclarationSyntax); + return MappingOptions.Create(context, typeDeclarationSyntax); } return null; @@ -48,10 +48,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private void GenerateCode(SourceProductionContext context, Compilation compilation, ImmutableArray mappingOptions) { - // generate partial classes with mapping methods + // generate partial classes/records with mapping methods foreach (var ma in mappingOptions) { - var className = ma.ClassDeclarationSyntax.Identifier.Text; + var className = ma.TypeDeclarationSyntax.Identifier.Text; + var typeKeyword = ma.IsRecord ? "record" : "class"; var sourceText = new SourceBuilder(); @@ -66,12 +67,12 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati sourceText.AppendLine(); sourceText.AppendLine($"namespace {ma.Namespace};"); sourceText.AppendLine(); - sourceText.AppendLine($"partial class {className}"); + sourceText.AppendLine($"partial {typeKeyword} {className}"); sourceText.AppendLine("{"); sourceText.IncreaseIndent(); // Get all properties including inherited ones - var allSourceProperties = MappingOptions.GetAllProperties(ma.SemanticModel, ma.ClassDeclarationSyntax); + var allSourceProperties = MappingOptions.GetAllProperties(ma.SemanticModel, ma.TypeDeclarationSyntax); // Get target properties from the symbol (works for both same-compilation and external assemblies) var allTargetProperties = MappingOptions.GetAllPropertiesFromSymbol(ma.TargetSymbol); diff --git a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs index 313819f..7942392 100644 --- a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs +++ b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs @@ -7,13 +7,14 @@ namespace TenJames.CompMap; using Microsoft.CodeAnalysis.CSharp.Syntax; public class MappingOptions { - public ClassDeclarationSyntax ClassDeclarationSyntax { get; set; } - public string ClassName => ClassDeclarationSyntax.Identifier.Text; + public TypeDeclarationSyntax TypeDeclarationSyntax { get; set; } + public string ClassName => TypeDeclarationSyntax.Identifier.Text; + public bool IsRecord => TypeDeclarationSyntax is RecordDeclarationSyntax; public string AttributeName { get; set; } public string Namespace { get; set; } // Target can be either from source (same compilation) or from metadata (external assembly) - public ClassDeclarationSyntax? TargetSyntax { get; set; } + public TypeDeclarationSyntax? TargetSyntax { get; set; } public INamedTypeSymbol TargetSymbol { get; set; } public string TargetName => TargetSymbol.Name; @@ -24,11 +25,11 @@ public class MappingOptions { public SemanticModel SemanticModel { get; set; } /// - /// Gets all properties including inherited ones from a class declaration (when source is available) + /// Gets all properties including inherited ones from a type declaration (when source is available) /// - public static List GetAllProperties(SemanticModel semanticModel, ClassDeclarationSyntax classDecl) + public static List GetAllProperties(SemanticModel semanticModel, TypeDeclarationSyntax typeDecl) { - var symbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol; + var symbol = semanticModel.GetDeclaredSymbol(typeDecl) as INamedTypeSymbol; return symbol == null ? new List() : GetAllPropertiesFromSymbol(symbol); } @@ -48,6 +49,10 @@ public static List GetAllPropertiesFromSymbol(INamedTypeSymbol sym foreach (var prop in typeProperties) { + // Skip EqualityContract property which is compiler-generated for records + if (prop.Name == "EqualityContract") + continue; + // Avoid duplicates (overridden properties) if (!properties.Any(p => p.Name == prop.Name)) { @@ -69,10 +74,10 @@ public static List GetAllPropertiesFromSymbol(INamedTypeSymbol sym public static MappingOptions? Create( GeneratorSyntaxContext context, - ClassDeclarationSyntax classDeclarationSyntax) + TypeDeclarationSyntax typeDeclarationSyntax) { - var ns = classDeclarationSyntax.FirstAncestorOrSelf(); - var fileScoped = classDeclarationSyntax.FirstAncestorOrSelf(); + var ns = typeDeclarationSyntax.FirstAncestorOrSelf(); + var fileScoped = typeDeclarationSyntax.FirstAncestorOrSelf(); var namespaceName = ns != null ? ns.Name.ToString() @@ -81,14 +86,14 @@ public static List GetAllPropertiesFromSymbol(INamedTypeSymbol sym : "GlobalNamespace"; - foreach (var attributeSyntax in classDeclarationSyntax.AttributeLists.SelectMany(attributeListSyntax => attributeListSyntax.Attributes)) + foreach (var attributeSyntax in typeDeclarationSyntax.AttributeLists.SelectMany(attributeListSyntax => attributeListSyntax.Attributes)) { var attributeName = attributeSyntax.Name.ToString(); if (AttributeDefinitions.GetAllAttributes().Select(x => x.Name).Any(x => attributeName.Contains(x))) { // Get the target type symbol INamedTypeSymbol? targetSymbol = null; - ClassDeclarationSyntax? targetSyntax = null; + TypeDeclarationSyntax? targetSyntax = null; if (attributeSyntax.ArgumentList?.Arguments.First().Expression is TypeOfExpressionSyntax typeOfExpression) { @@ -99,7 +104,7 @@ public static List GetAllPropertiesFromSymbol(INamedTypeSymbol sym if (targetSymbol != null) { var syntaxRef = targetSymbol.DeclaringSyntaxReferences.FirstOrDefault(); - targetSyntax = syntaxRef?.GetSyntax() as ClassDeclarationSyntax; + targetSyntax = syntaxRef?.GetSyntax() as TypeDeclarationSyntax; } } @@ -114,7 +119,7 @@ public static List GetAllPropertiesFromSymbol(INamedTypeSymbol sym : targetSymbol.ContainingNamespace.ToDisplayString(); return new MappingOptions { - ClassDeclarationSyntax = classDeclarationSyntax, + TypeDeclarationSyntax = typeDeclarationSyntax, AttributeName = attributeName, Namespace = namespaceName, TargetSyntax = targetSyntax,