diff --git a/Readme.md b/Readme.md
index cda2b25..ab48fdd 100644
--- a/Readme.md
+++ b/Readme.md
@@ -105,7 +105,6 @@ file:
Include="TenJames.CompMap"
Version="latest_version"
OutputItemType="Analyzer"
- ReferenceOutputAssembly="false"
/>
```
diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs
index fa1eb56..1b54106 100644
--- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs
+++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/MappingIntegrationTests.cs
@@ -3,7 +3,7 @@ namespace TenJames.CompMap.IntegrationTests;
using System;
using System.Collections.Generic;
using System.Linq;
-using Mappper;
+using TenJames.CompMap.Mapper;
using Xunit;
public class MappingIntegrationTests
diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj
index 5fe7fe3..9a1ab49 100644
--- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj
+++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TenJames.CompMap.IntegrationTests.csproj
@@ -21,7 +21,7 @@
+ />
diff --git a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs
index 056c01c..8e2bd1a 100644
--- a/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs
+++ b/TenJames.CompMap/TenJames.CompMap.IntegrationTests/TestDtos.cs
@@ -5,7 +5,7 @@ namespace TenJames.CompMap.IntegrationTests;
using System.Globalization;
using System.Linq;
using Attributes;
-using Mappper;
+using Mapper;
///
/// DTO for reading product data
diff --git a/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs b/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs
index cbd049a..3597e6f 100644
--- a/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs
+++ b/TenJames.CompMap/TenJames.CompMap.Tests/MapperGeneratorTests.cs
@@ -9,78 +9,6 @@ namespace TenJames.CompMap.Tests;
public class MapperGeneratorTests
{
- [Fact]
- public void AttributeGenerator_ShouldGenerateMapFromAttribute()
- {
- // Arrange
- var attributeGenerator = new AttributeGenerator();
- var driver = CSharpGeneratorDriver.Create(attributeGenerator);
- var compilation = CSharpCompilation.Create(
- nameof(AttributeGenerator_ShouldGenerateMapFromAttribute),
- references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }
- );
-
- // Act
- var runResult = driver.RunGenerators(compilation).GetRunResult();
-
- // Assert
- var generatedAttribute = runResult.GeneratedTrees
- .FirstOrDefault(t => t.FilePath.EndsWith("MapFromAttribute.g.cs", StringComparison.Ordinal));
-
- Assert.NotNull(generatedAttribute);
- var generatedCode = generatedAttribute.GetText().ToString();
- Assert.Contains("public class MapFromAttribute", generatedCode);
- Assert.Contains("Type sourceType", generatedCode);
- }
-
- [Fact]
- public void AttributeGenerator_ShouldGenerateMapToAttribute()
- {
- // Arrange
- var attributeGenerator = new AttributeGenerator();
- var driver = CSharpGeneratorDriver.Create(attributeGenerator);
- var compilation = CSharpCompilation.Create(
- nameof(AttributeGenerator_ShouldGenerateMapToAttribute),
- references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }
- );
-
- // Act
- var runResult = driver.RunGenerators(compilation).GetRunResult();
-
- // Assert
- var generatedAttribute = runResult.GeneratedTrees
- .FirstOrDefault(t => t.FilePath.EndsWith("MapToAttribute.g.cs", StringComparison.Ordinal));
-
- Assert.NotNull(generatedAttribute);
- var generatedCode = generatedAttribute.GetText().ToString();
- Assert.Contains("public class MapToAttribute", generatedCode);
- Assert.Contains("Type destinationType", generatedCode);
- }
-
- [Fact]
- public void AttributeGenerator_ShouldGenerateMapperInterface()
- {
- // Arrange
- var attributeGenerator = new AttributeGenerator();
- var driver = CSharpGeneratorDriver.Create(attributeGenerator);
- var compilation = CSharpCompilation.Create(
- nameof(AttributeGenerator_ShouldGenerateMapperInterface),
- references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }
- );
-
- // Act
- var runResult = driver.RunGenerators(compilation).GetRunResult();
-
- // Assert
- var generatedMapper = runResult.GeneratedTrees
- .FirstOrDefault(t => t.FilePath.EndsWith("Mapper.g.cs", StringComparison.Ordinal));
-
- Assert.NotNull(generatedMapper);
- var generatedCode = generatedMapper.GetText().ToString();
- Assert.Contains("public interface IMapper", generatedCode);
- Assert.Contains("public class BaseMapper : IMapper", generatedCode);
- Assert.Contains("TDestination Map(object source)", generatedCode);
- }
[Fact]
public void MapperGenerator_ShouldRunWithoutErrors()
@@ -106,7 +34,7 @@ public partial class Target
}";
var compilation = CreateCompilation(sourceCode);
- var generators = new IIncrementalGenerator[] { new AttributeGenerator(), new MapperGenerator() };
+ var generators = new IIncrementalGenerator[] { new MapperGenerator() };
var driver = CSharpGeneratorDriver.Create(generators);
// Act
@@ -120,7 +48,7 @@ public partial class Target
Assert.Empty(errors);
// Check that some code was generated
- Assert.True(outputCompilation.SyntaxTrees.Count() > 1, "Generator should produce additional syntax trees");
+ Assert.True(outputCompilation.SyntaxTrees.Any(), "Generator should produce additional syntax trees");
}
private static CSharpCompilation CreateCompilation(string source)
diff --git a/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs b/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs
index 2f3a92f..04b374e 100644
--- a/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs
+++ b/TenJames.CompMap/TenJames.CompMap/AttributeDefinition.cs
@@ -5,7 +5,7 @@ namespace TenJames.CompMap;
///
/// Information about a mapping attribute.
///
-public class AttributeDefinition
+internal class AttributeDefinition
{
///
/// Name of the attribute.
@@ -26,7 +26,7 @@ public class AttributeDefinition
///
/// Information about an argument for a mapping attribute.
///
-public class ArgumentDefinition
+internal class ArgumentDefinition
{
///
/// Name of the argument.
@@ -47,7 +47,7 @@ public class ArgumentDefinition
///
/// Static class containing predefined attribute definitions.
///
-public static class AttributeDefinitions
+internal static class AttributeDefinitions
{
private static readonly AttributeDefinition MapFrom = new()
{
diff --git a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs b/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs
deleted file mode 100644
index 7300266..0000000
--- a/TenJames.CompMap/TenJames.CompMap/AttributeGenerator.cs
+++ /dev/null
@@ -1,238 +0,0 @@
-namespace TenJames.CompMap;
-
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Text;
-
-///
-[Generator]
-public class AttributeGenerator : IIncrementalGenerator
-{
-
- ///
- public void Initialize(IncrementalGeneratorInitializationContext context) => RegisterAttributes(context);
-
- private static void RegisterAttributes(IncrementalGeneratorInitializationContext context) =>
- context.RegisterPostInitializationOutput(ctx =>
- {
- var attributes = AttributeDefinitions.GetAllAttributes();
- foreach (var attribute in attributes)
- {
- var sourceText = GenerateAttributeSourceText(attribute);
- ctx.AddSource($"{attribute.Name}Attribute.g.cs", sourceText);
- }
-
- // Generate modifier attributes (like AutoPropertyChain)
- var modifierAttributes = AttributeDefinitions.GetAllModifierAttributes();
- foreach (var attribute in modifierAttributes)
- {
- var sourceText = GenerateModifierAttributeSourceText(attribute);
- ctx.AddSource($"{attribute.Name}Attribute.g.cs", sourceText);
- }
-
- ctx.AddSource("Mapper.g.cs", GenerateMapperClass());
- });
-
- private static SourceText GenerateMapperClass()
- {
- var sourceText = SourceText.From(
- $$"""
- //
- #nullable enable
- using System;
- using System.Collections.Generic;
- using System.Reflection;
-
- namespace TenJames.CompMap.Mappper;
-
- ///
- /// Interface for mapping between types.
- ///
- public interface IMapper
- {
- ///
- /// Tries to map the source object to the destination type.
- /// If the mapping is not defined, by default an exception is thrown.
- ///
- /// Source for the mapping
- /// Destination type for the map
- ///
- TDestination Map(object source);
- }
-
-
- ///
- /// Base implementation of IMapper
- ///
- public class BaseMapper : IMapper {
-
- ///
- /// Default implementation for null source
- ///
- /// Destination type
- ///
- protected virtual TDestination OnNull()
- {
- return default(TDestination);
- }
-
- ///
- /// Default implementation for mapping
- ///
- /// the source
- /// Method on Source
- /// Method on destination
- /// Destination type
- ///
- protected virtual TDestination OnMap(object source, MethodInfo? mapToMethod, MethodInfo? mapFromMethod)
- {
- if (mapToMethod != null && mapToMethod.ReturnType == typeof(TDestination))
- {
- return (TDestination)mapToMethod.Invoke(source, new object[] { this });
- }
-
- // if TDestination has a static method MapFrom
- if (mapFromMethod != null)
- {
- return (TDestination)mapFromMethod.Invoke(null, new object[] { this, source });
- }
-
- return OnError(source, new NotImplementedException($"No mapping defined from {source.GetType().FullName} to {typeof(TDestination).FullName}"));
- }
-
- ///
- /// An error occurred during mapping
- ///
- ///
- ///
- /// Destination type
- /// By default, it always throw
- /// By default, it throws an error
- protected virtual TDestination OnError(object source, Exception ex)
- {
- throw ex;
- }
-
- ///
- /// What should happen when source is enumerable
- ///
- ///
- /// Destination type
- /// How should collection be mapped
- protected virtual TDestination OnEnumerable(object source)
- {
- var enumerable = (System.Collections.IEnumerable)source;
-
- if (typeof(TDestination).IsArray)
- {
- var elementType = typeof(TDestination).GetElementType();
- var listType = typeof(List<>).MakeGenericType(elementType);
- var list = (System.Collections.IList)Activator.CreateInstance(listType);
- foreach (var item in enumerable)
- {
- var mappedItem = this.GetType().GetMethod("Map").MakeGenericMethod(elementType).Invoke(this, new object[] { item });
- list.Add(mappedItem);
- }
- var array = Array.CreateInstance(elementType, list.Count);
- list.CopyTo(array, 0);
- return (TDestination)(object)array;
- }
- if (typeof(TDestination).IsGenericType && ( typeof(TDestination).GetGenericTypeDefinition() == typeof(List<>) ||
- typeof(TDestination).GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
- typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) ||
- false
- ))
- {
- var elementType = typeof(TDestination).GetGenericArguments()[0];
- var list = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
- foreach (var item in enumerable)
- {
- var mappedItem = this.GetType().GetMethod("Map").MakeGenericMethod(elementType).Invoke(this, new object[] { item });
- list.Add(mappedItem);
- }
- return (TDestination)list;
- }
- else
- {
- return OnError(source, new NotImplementedException($"Mapping to collection type {typeof(TDestination).FullName} is not supported."));
- }
- }
-
-
- ///
- public virtual TDestination Map(object source)
- {
- switch (source)
- {
- case null:
- return OnNull();
-
- // if source is iEnumerable
- case System.Collections.IEnumerable enumerable:
- return OnEnumerable(source);
- }
-
-
- // if source has a mapping method to TDestination, use it
- var mapMethod = source.GetType().GetMethod("MapTo", new Type[] { typeof(IMapper) });
- var mapFromMethod = typeof(TDestination).GetMethod("MapFrom", new Type[] { typeof(IMapper), source.GetType() });
-
- return OnMap(source, mapMethod, mapFromMethod);
- }
- }
-
- """,
- Encoding.UTF8);
- return sourceText;
- }
-
- private static SourceText GenerateAttributeSourceText(AttributeDefinition attribute)
- {
- var src = new StringBuilder();
- src.AppendLine("// ");
- src.AppendLine("using System;");
- src.AppendLine();
- src.AppendLine($"namespace {Consts.AttributesNamespace}");
- src.AppendLine("{");
- src.AppendLine($" /// ");
- src.AppendLine($" /// {attribute.Description}");
- src.AppendLine($" /// ");
- src.AppendLine($" [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]");
- src.AppendLine($" public class {attribute.Name}Attribute (");
- foreach (var arg in attribute.Arguments)
- {
- var comma = arg != attribute.Arguments.Last() ? "," : "";
- src.AppendLine($" {arg.Type} {arg.Name}{comma} // {arg.Value}");
- }
- for (var i = 0; i < attribute.Arguments.Count; i++)
- {
- var arg = attribute.Arguments[i];
- var comma = i < attribute.Arguments.Count - 1 ? "," : "";
- }
-
- src.AppendLine(" ): Attribute{");
- src.AppendLine(" }");
- src.AppendLine("}");
- return SourceText.From(src.ToString(), Encoding.UTF8);
- }
-
- private static SourceText GenerateModifierAttributeSourceText(AttributeDefinition attribute)
- {
- var src = new StringBuilder();
- src.AppendLine("// ");
- src.AppendLine("using System;");
- src.AppendLine();
- src.AppendLine($"namespace {Consts.AttributesNamespace}");
- src.AppendLine("{");
- src.AppendLine($" /// ");
- src.AppendLine($" /// {attribute.Description}");
- src.AppendLine($" /// ");
- src.AppendLine($" [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]");
- src.AppendLine($" public class {attribute.Name}Attribute : Attribute");
- src.AppendLine(" {");
- src.AppendLine(" }");
- src.AppendLine("}");
- return SourceText.From(src.ToString(), Encoding.UTF8);
- }
-}
diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs
new file mode 100644
index 0000000..fe373a0
--- /dev/null
+++ b/TenJames.CompMap/TenJames.CompMap/Attributes/AutoPropertyChainAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace TenJames.CompMap.Attributes
+{
+///
+/// Enables automatic property chain mapping. Maps flattened properties like CategoryName to nested properties like Category.Name.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+public class AutoPropertyChainAttribute : Attribute
+{
+}
+}
diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs
new file mode 100644
index 0000000..c151f0c
--- /dev/null
+++ b/TenJames.CompMap/TenJames.CompMap/Attributes/MapFromAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace TenJames.CompMap.Attributes
+{
+///
+/// Indicates that the decorated class can be mapped from the specified source type.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+public class MapFromAttribute (
+ Type sourceType // The source type to map from.
+): Attribute{
+}
+}
diff --git a/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs b/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs
new file mode 100644
index 0000000..b1cf2ce
--- /dev/null
+++ b/TenJames.CompMap/TenJames.CompMap/Attributes/MapToAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace TenJames.CompMap.Attributes
+{
+///
+/// Indicates that the decorated class can be mapped to the specified destination type.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+public class MapToAttribute (
+ Type destinationType // The destination type to map to.
+): Attribute{
+}
+}
diff --git a/TenJames.CompMap/TenJames.CompMap/Consts.cs b/TenJames.CompMap/TenJames.CompMap/Consts.cs
index b98065d..29f026a 100644
--- a/TenJames.CompMap/TenJames.CompMap/Consts.cs
+++ b/TenJames.CompMap/TenJames.CompMap/Consts.cs
@@ -3,16 +3,12 @@ namespace TenJames.CompMap;
///
/// Internal constants used throughout the CompMap library
///
-public static class Consts
+internal static class Consts
{
- ///
- /// Attributes namespace
- ///
- public const string AttributesNamespace = "TenJames.CompMap.Attributes";
///
/// Mapper namespace
///
- public const string MapperNamespace = "TenJames.CompMap.Mappper";
+ public const string MapperNamespace = "TenJames.CompMap.Mapper";
}
diff --git a/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs b/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs
new file mode 100644
index 0000000..d4666d0
--- /dev/null
+++ b/TenJames.CompMap/TenJames.CompMap/InternalsVisibleTo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("TenJames.CompMap.Tests")]
diff --git a/TenJames.CompMap/TenJames.CompMap/Mapper.cs b/TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs
similarity index 99%
rename from TenJames.CompMap/TenJames.CompMap/Mapper.cs
rename to TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs
index 58e648e..238f000 100644
--- a/TenJames.CompMap/TenJames.CompMap/Mapper.cs
+++ b/TenJames.CompMap/TenJames.CompMap/Mapper/Mapper.cs
@@ -1,6 +1,6 @@
//
#nullable enable
-namespace TenJames.CompMap.Mappper;
+namespace TenJames.CompMap.Mapper;
using System;
using System.Collections;
diff --git a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs
index 375ec41..4f2ed04 100644
--- a/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs
+++ b/TenJames.CompMap/TenJames.CompMap/MapperGenerator.cs
@@ -13,7 +13,7 @@ namespace TenJames.CompMap;
/// Mapper generator that creates mapping methods based on attributes
///
[Generator]
-public class MapperGenerator : IIncrementalGenerator
+internal class MapperGenerator : IIncrementalGenerator
{
///
diff --git a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs
index 535ab94..0f69f53 100644
--- a/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs
+++ b/TenJames.CompMap/TenJames.CompMap/MappingOptions.cs
@@ -6,7 +6,7 @@ namespace TenJames.CompMap;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-public class MappingOptions
+internal class MappingOptions
{
public TypeDeclarationSyntax TypeDeclarationSyntax { get; set; }
diff --git a/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs b/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs
index 33db54a..248ee7f 100644
--- a/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs
+++ b/TenJames.CompMap/TenJames.CompMap/SourceBuilder.cs
@@ -6,7 +6,7 @@ namespace TenJames.CompMap;
///
/// SourceBuilder
///
-public class SourceBuilder
+internal class SourceBuilder
{
private readonly StringBuilder sourceText;
diff --git a/example/Example.Console/Program.cs b/example/Example.Console/Program.cs
index 0b0b46e..ebe9467 100644
--- a/example/Example.Console/Program.cs
+++ b/example/Example.Console/Program.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using Example.DTOS;
using Example.Entities;
-using TenJames.CompMap.Mappper;
+using TenJames.CompMap.Mapper;
var mapper = new BaseMapper();
var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
@@ -31,7 +31,7 @@
Console.WriteLine("\n2. UserReadDto (from Example.DTOS assembly):");
Console.WriteLine(JsonSerializer.Serialize(userDto, jsonOptions));
-Console.WriteLine("\n=== Cross-assembly mapping works! ===");
+Console.WriteLine("\n=== 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");
diff --git a/example/Example.DTOS/Example.DTOS.csproj b/example/Example.DTOS/Example.DTOS.csproj
index 73c8c91..aae97b8 100644
--- a/example/Example.DTOS/Example.DTOS.csproj
+++ b/example/Example.DTOS/Example.DTOS.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/example/Example.DTOS/UserDto.cs b/example/Example.DTOS/UserDto.cs
index 8aea4e4..2709e66 100644
--- a/example/Example.DTOS/UserDto.cs
+++ b/example/Example.DTOS/UserDto.cs
@@ -2,7 +2,7 @@ namespace Example.DTOS;
using Entities;
using TenJames.CompMap.Attributes;
-using TenJames.CompMap.Mappper;
+using TenJames.CompMap.Mapper;
///
/// DTO for reading user data - maps FROM the User entity