From ab4f9279969da0ceff28ba5c86bc9644d1a6f28a Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 15:59:19 +0100 Subject: [PATCH 01/15] feat: added AccessModifier property to the GenerateDataReaderMapper attribute --- MapDataReader/MapperGenerator.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 07e3b91..ad16e52 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -11,6 +11,17 @@ namespace MapDataReader [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class GenerateDataReaderMapperAttribute : Attribute { + public string AccessModifier { get; set; } + + public GenerateDataReaderMapperAttribute() + { + AccessModifier = "public"; + } + + public GenerateDataReaderMapperAttribute(string access = "public") + { + AccessModifier = access; + } } [Generator] From 8dab45155f8e4dc88b025b4e002a74cf56ad9c71 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 15:59:33 +0100 Subject: [PATCH 02/15] feat: added method to get the access modifier value --- MapDataReader/MapperGenerator.cs | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index ad16e52..1c72fcd 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -130,6 +130,44 @@ public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new TargetTypeTracker()); } + + private string GetAccessModifer(ClassDeclarationSyntax typeNode) + { + // Retrieve the attribute list + var attributeList = typeNode.AttributeLists + .SelectMany(al => al.Attributes) + .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper"); + + if (attributeList?.ArgumentList == null) + return "public"; + + var arguments = attributeList.ArgumentList.Arguments; + + if (arguments.Count == 0) + return "public"; + + if (arguments.Count == 1) + { + var argumentExpr = arguments[0].Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; + } + + foreach (var argument in arguments) + { + // Check if the argument is a named argument + if (argument is AttributeArgumentSyntax attributeArgument) + { + var nameEquals = attributeArgument.NameEquals; + if (nameEquals?.Name.Identifier.Text == "AccessModifier") + { + var argumentExpr = argument.Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; + } + } + } + + return "public"; + } } internal class TargetTypeTracker : ISyntaxContextReceiver From 7d7e83b589cbf1916091abce431018f78f2201b7 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 15:59:47 +0100 Subject: [PATCH 03/15] feat: using the access modifier for SetPropertyByName and To methods --- MapDataReader/MapperGenerator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 1c72fcd..6863dee 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -39,6 +39,8 @@ public void Execute(GeneratorExecutionContext context) var allProperties = typeNodeSymbol.GetAllSettableProperties(); + var accessModifier = GetAccessModifer(typeNode); + var src = $@" // #pragma warning disable 8019 //disable 'unnecessary using directive' warning @@ -51,7 +53,7 @@ namespace MapDataReader {{ public static partial class MapperExtensions {{ - public static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value) + {accessModifier} static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value) {{ SetPropertyByUpperName(target, name.ToUpperInvariant(), value); }} @@ -90,7 +92,7 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ { src += $@" - public static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) + {accessModifier} static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) {{ var list = new List<{typeNodeSymbol.FullName()}>(); From 238644154548142d7ad81621f9887406b292bad8 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 16:00:09 +0100 Subject: [PATCH 04/15] test: added tests for the new access modifer Tests added to test the generation and usage --- MapDataReader.Benchmarks/Program.cs | 2 +- MapDataReader.Tests/TestActualCode.cs | 31 +++++++++++++++++++++++++++ MapDataReader.Tests/TestGenerator.cs | 23 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/MapDataReader.Benchmarks/Program.cs b/MapDataReader.Benchmarks/Program.cs index d402e0c..bf3afe3 100644 --- a/MapDataReader.Benchmarks/Program.cs +++ b/MapDataReader.Benchmarks/Program.cs @@ -93,7 +93,7 @@ public static void Setup() } } - [GenerateDataReaderMapper] + [GenerateDataReaderMapper(AccessModifier = "internal")] public class TestClass { public string String1 { get; set; } diff --git a/MapDataReader.Tests/TestActualCode.cs b/MapDataReader.Tests/TestActualCode.cs index 76ccaca..19a2054 100644 --- a/MapDataReader.Tests/TestActualCode.cs +++ b/MapDataReader.Tests/TestActualCode.cs @@ -3,6 +3,7 @@ using System.Data; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -227,6 +228,24 @@ public void TestWrongProperty() o.SetPropertyByName("Name", 123); //try to assign string prop to int Assert.IsTrue(o.Name == null); //wrong type. should be null } + + [TestMethod] + public void TestInternalAccessModifier() + { + var type = typeof(MapperExtensions); + var method = type.GetMethod("ToTestClassInternal", BindingFlags.Static | BindingFlags.NonPublic); + + Assert.IsNotNull(method, "Expected method 'ToTestClassInternal' to be 'internal'."); + } + + [TestMethod] + public void TestInternalAccessModifierNamed() + { + var type = typeof(MapperExtensions); + var method = type.GetMethod("ToTestClassInternalNamed", BindingFlags.Static | BindingFlags.NonPublic); + + Assert.IsNotNull(method, "Expected method 'ToTestClassInternalNamed' to be 'internal'."); + } } public class BaseClass @@ -239,5 +258,17 @@ public class ChildClass : BaseClass { public string Name { get; set; } } + + [GenerateDataReaderMapper("internal")] + internal class TestClassInternal + { + public int Id { get; set; } + } + + [GenerateDataReaderMapper(AccessModifier = "internal")] + internal class TestClassInternalNamed + { + public int Id { get; set; } + } } diff --git a/MapDataReader.Tests/TestGenerator.cs b/MapDataReader.Tests/TestGenerator.cs index 083790b..8ae70aa 100644 --- a/MapDataReader.Tests/TestGenerator.cs +++ b/MapDataReader.Tests/TestGenerator.cs @@ -29,6 +29,29 @@ public class MyClass public decimal Price {get;set;} } } +"; + var src = GetAndCheckOutputSource(userSource); + } + + [TestMethod] + public void TestAccessModifier() + { + string userSource = @" +using MapDataReader; + +namespace MyCode +{ + [GenerateDataReaderMapper(AccessModifier = ""internal"")] + public class MyClass + { + public string Name {get;set;} + public int Size {get;set;} + public bool Enabled {get;set;} + public System.DateTime Created {get;set;} + public System.DateTimeOffset Offset {get;set;} + public decimal Price {get;set;} + } +} "; var src = GetAndCheckOutputSource(userSource); } From acee9ce4b2984c61087ed94830698ad3da9205e9 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 16:00:53 +0100 Subject: [PATCH 05/15] refactor: fixed typo --- MapDataReader.Tests/TestActualCode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MapDataReader.Tests/TestActualCode.cs b/MapDataReader.Tests/TestActualCode.cs index 19a2054..2c71391 100644 --- a/MapDataReader.Tests/TestActualCode.cs +++ b/MapDataReader.Tests/TestActualCode.cs @@ -148,7 +148,7 @@ public void TestStringAssign() } [TestMethod] - public void TestDatatReader() + public void TestDataReader() { //create datatable with test data var dt = new DataTable(); From 42b5809c7b5d5a71c57f2d71de606824fc50604b Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 2 Oct 2024 17:47:02 +0100 Subject: [PATCH 06/15] docs: updated documentation with details of the new access modifier --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index ab78f76..21c89e8 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,20 @@ Some notes for the above * Properly maps `DBNull` to `null`. * Complex-type properties may not work. +### Access Modifier: `public` or `internal` + +You can now specify the access modifer to be used with the mapping methods. By default, the methods will be `public` for backwards compatability. + +For example, to prevent exposure outside your assembly you'd set it to `internal`. This would hide the mapping methods outside your model project: + +``` csharp +[GenerateDataReaderMapper("internal")] +public class MyClass +{ + public int ID { get; set; } +... +``` + ## Bonus API: `SetPropertyByName` This package also adds a super fast `SetPropertyByName` extension method generated at compile time for your class. From 55137ff61d90788dbf95bd9bb6675fbd25dfbff4 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:28:56 +0100 Subject: [PATCH 07/15] feat: updated to latest C# language --- MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj | 1 + MapDataReader.Tests/MapDataReader.Tests.csproj | 2 +- MapDataReader/MapDataReader.csproj | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj b/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj index 1073b53..44cba17 100644 --- a/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj +++ b/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj @@ -5,6 +5,7 @@ net6.0 enable enable + latest diff --git a/MapDataReader.Tests/MapDataReader.Tests.csproj b/MapDataReader.Tests/MapDataReader.Tests.csproj index b14646f..033eb5a 100644 --- a/MapDataReader.Tests/MapDataReader.Tests.csproj +++ b/MapDataReader.Tests/MapDataReader.Tests.csproj @@ -4,8 +4,8 @@ net6.0 enable enable - false + latest diff --git a/MapDataReader/MapDataReader.csproj b/MapDataReader/MapDataReader.csproj index 7fda524..286c9e4 100644 --- a/MapDataReader/MapDataReader.csproj +++ b/MapDataReader/MapDataReader.csproj @@ -14,6 +14,7 @@ aot;source-generator Super fast mapping of DataReader to custom objects True + latest From 2970df21373195cb8cb1ec30f366ec09df47e834 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:31:34 +0100 Subject: [PATCH 08/15] refactor: separated attribute and helper classes into new files --- .../GenerateDataReaderMapperAttribute.cs | 19 +++++ MapDataReader/Helpers.cs | 52 +++++++++++++ MapDataReader/MapperGenerator.cs | 74 +------------------ MapDataReader/TargetTypeTracker.cs | 18 +++++ 4 files changed, 91 insertions(+), 72 deletions(-) create mode 100644 MapDataReader/GenerateDataReaderMapperAttribute.cs create mode 100644 MapDataReader/Helpers.cs create mode 100644 MapDataReader/TargetTypeTracker.cs diff --git a/MapDataReader/GenerateDataReaderMapperAttribute.cs b/MapDataReader/GenerateDataReaderMapperAttribute.cs new file mode 100644 index 0000000..ed26c72 --- /dev/null +++ b/MapDataReader/GenerateDataReaderMapperAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace MapDataReader; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class GenerateDataReaderMapperAttribute : Attribute +{ + public string AccessModifier { get; set; } + + public GenerateDataReaderMapperAttribute() + { + AccessModifier = "public"; + } + + public GenerateDataReaderMapperAttribute(string access = "public") + { + AccessModifier = access; + } +} \ No newline at end of file diff --git a/MapDataReader/Helpers.cs b/MapDataReader/Helpers.cs new file mode 100644 index 0000000..301a283 --- /dev/null +++ b/MapDataReader/Helpers.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MapDataReader; + +internal static class Helpers +{ + internal static bool IsDecoratedWithAttribute(this TypeDeclarationSyntax cdecl, string attributeName) => + cdecl.AttributeLists + .SelectMany(x => x.Attributes) + .Any(x => x.Name.ToString().Contains(attributeName)); + + + internal static string FullName(this ITypeSymbol typeSymbol) => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + internal static string StringConcat(this IEnumerable source, string separator) => string.Join(separator, source); + + // returns all properties with public setters + internal static IEnumerable GetAllSettableProperties(this ITypeSymbol typeSymbol) + { + var result = typeSymbol + .GetMembers() + .Where(s => s.Kind == SymbolKind.Property).Cast() //get all properties + .Where(p => p.SetMethod?.DeclaredAccessibility == Accessibility.Public) //has a public setter? + .ToList(); + + //now get the base class + var baseType = typeSymbol.BaseType; + if (baseType != null) + result.AddRange(baseType.GetAllSettableProperties()); //recursion + + return result; + } + + //checks if type is a nullable num + internal static bool IsNullableEnum(this ITypeSymbol symbol) + { + //tries to get underlying non-nullable type from nullable type + //and then check if it's Enum + if (symbol.NullableAnnotation == NullableAnnotation.Annotated + && symbol is INamedTypeSymbol namedType + && namedType.IsValueType + && namedType.IsGenericType + && namedType.ConstructedFrom?.ToDisplayString() == "System.Nullable" + ) + return namedType.TypeArguments[0].TypeKind == TypeKind.Enum; + + return false; + } +} \ No newline at end of file diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 6863dee..0510463 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -8,22 +8,6 @@ namespace MapDataReader { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class GenerateDataReaderMapperAttribute : Attribute - { - public string AccessModifier { get; set; } - - public GenerateDataReaderMapperAttribute() - { - AccessModifier = "public"; - } - - public GenerateDataReaderMapperAttribute(string access = "public") - { - AccessModifier = access; - } - } - [Generator] public class MapperGenerator : ISourceGenerator { @@ -172,61 +156,7 @@ private string GetAccessModifer(ClassDeclarationSyntax typeNode) } } - internal class TargetTypeTracker : ISyntaxContextReceiver - { - public IImmutableList TypesNeedingGening = ImmutableList.Create(); + - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) - { - if (context.Node is ClassDeclarationSyntax cdecl) - if (cdecl.IsDecoratedWithAttribute("GenerateDataReaderMapper")) - TypesNeedingGening = TypesNeedingGening.Add(cdecl); - } - } - - internal static class Helpers - { - internal static bool IsDecoratedWithAttribute(this TypeDeclarationSyntax cdecl, string attributeName) => - cdecl.AttributeLists - .SelectMany(x => x.Attributes) - .Any(x => x.Name.ToString().Contains(attributeName)); - - - internal static string FullName(this ITypeSymbol typeSymbol) => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - - internal static string StringConcat(this IEnumerable source, string separator) => string.Join(separator, source); - - // returns all properties with public setters - internal static IEnumerable GetAllSettableProperties(this ITypeSymbol typeSymbol) - { - var result = typeSymbol - .GetMembers() - .Where(s => s.Kind == SymbolKind.Property).Cast() //get all properties - .Where(p => p.SetMethod?.DeclaredAccessibility == Accessibility.Public) //has a public setter? - .ToList(); - - //now get the base class - var baseType = typeSymbol.BaseType; - if (baseType != null) - result.AddRange(baseType.GetAllSettableProperties()); //recursion - - return result; - } - - //checks if type is a nullable num - internal static bool IsNullableEnum(this ITypeSymbol symbol) - { - //tries to get underlying non-nullable type from nullable type - //and then check if it's Enum - if (symbol.NullableAnnotation == NullableAnnotation.Annotated - && symbol is INamedTypeSymbol namedType - && namedType.IsValueType - && namedType.IsGenericType - && namedType.ConstructedFrom?.ToDisplayString() == "System.Nullable" - ) - return namedType.TypeArguments[0].TypeKind == TypeKind.Enum; - - return false; - } - } + } \ No newline at end of file diff --git a/MapDataReader/TargetTypeTracker.cs b/MapDataReader/TargetTypeTracker.cs new file mode 100644 index 0000000..7cf21dd --- /dev/null +++ b/MapDataReader/TargetTypeTracker.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MapDataReader; + +internal class TargetTypeTracker : ISyntaxContextReceiver +{ + public IImmutableList TypesNeedingGening = ImmutableList.Create(); + + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + if (context.Node is not ClassDeclarationSyntax classDec) return; + + if (classDec.IsDecoratedWithAttribute("GenerateDataReaderMapper")) + TypesNeedingGening = TypesNeedingGening.Add(classDec); + } +} \ No newline at end of file From c009bafbf04984298a7cc746c9f195dfe4d4d1b8 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:37:27 +0100 Subject: [PATCH 09/15] feat: added new attributes for Namespace --- MapDataReader/GenerateDataReaderMapperAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MapDataReader/GenerateDataReaderMapperAttribute.cs b/MapDataReader/GenerateDataReaderMapperAttribute.cs index ed26c72..210cd12 100644 --- a/MapDataReader/GenerateDataReaderMapperAttribute.cs +++ b/MapDataReader/GenerateDataReaderMapperAttribute.cs @@ -6,6 +6,11 @@ namespace MapDataReader; public class GenerateDataReaderMapperAttribute : Attribute { public string AccessModifier { get; set; } + + /// + /// Gets or sets the namespace to be used in the generated methods. + /// + public string Namespace { get; set; } public GenerateDataReaderMapperAttribute() { From 3e9555097ae36df2f6121963bba164ccddd07551 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:40:32 +0100 Subject: [PATCH 10/15] refactor: changed to use file-scoped namespace --- MapDataReader/MapperGenerator.cs | 111 +++++++++++++++---------------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 0510463..3e12b8b 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -6,26 +6,26 @@ using System.Collections.Immutable; using System.Linq; -namespace MapDataReader +namespace MapDataReader; + +[Generator] +public class MapperGenerator : ISourceGenerator { - [Generator] - public class MapperGenerator : ISourceGenerator + public void Execute(GeneratorExecutionContext context) { - public void Execute(GeneratorExecutionContext context) - { - var targetTypeTracker = context.SyntaxContextReceiver as TargetTypeTracker; + var targetTypeTracker = context.SyntaxContextReceiver as TargetTypeTracker; - foreach (var typeNode in targetTypeTracker.TypesNeedingGening) - { - var typeNodeSymbol = context.Compilation - .GetSemanticModel(typeNode.SyntaxTree) - .GetDeclaredSymbol(typeNode); + foreach (var typeNode in targetTypeTracker.TypesNeedingGening) + { + var typeNodeSymbol = context.Compilation + .GetSemanticModel(typeNode.SyntaxTree) + .GetDeclaredSymbol(typeNode); - var allProperties = typeNodeSymbol.GetAllSettableProperties(); + var allProperties = typeNodeSymbol.GetAllSettableProperties(); - var accessModifier = GetAccessModifer(typeNode); + var accessModifier = GetAccessModifer(typeNode); - var src = $@" + var src = $@" // #pragma warning disable 8019 //disable 'unnecessary using directive' warning using System; @@ -72,9 +72,9 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ }} //end method"; - if (typeNodeSymbol.InstanceConstructors.Any(c => !c.Parameters.Any())) //has a constructor without parameters? - { - src += $@" + if (typeNodeSymbol.InstanceConstructors.Any(c => !c.Parameters.Any())) //has a constructor without parameters? + { + src += $@" {accessModifier} static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) {{ @@ -102,61 +102,56 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ dr.Close(); return list; }}"; - } + } - src += "\n}"; //end class - src += "\n}"; //end namespace + src += "\n}"; //end class + src += "\n}"; //end namespace - // Add the source code to the compilation - context.AddSource($"{typeNodeSymbol.Name}DataReaderMapper.g.cs", src); - } + // Add the source code to the compilation + context.AddSource($"{typeNodeSymbol.Name}DataReaderMapper.g.cs", src); } + } - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new TargetTypeTracker()); - } + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new TargetTypeTracker()); + } - private string GetAccessModifer(ClassDeclarationSyntax typeNode) - { - // Retrieve the attribute list - var attributeList = typeNode.AttributeLists - .SelectMany(al => al.Attributes) - .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper"); + private string GetAccessModifer(ClassDeclarationSyntax typeNode) + { + // Retrieve the attribute list + var attributeList = typeNode.AttributeLists + .SelectMany(al => al.Attributes) + .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper"); - if (attributeList?.ArgumentList == null) - return "public"; + if (attributeList?.ArgumentList == null) + return "public"; - var arguments = attributeList.ArgumentList.Arguments; + var arguments = attributeList.ArgumentList.Arguments; - if (arguments.Count == 0) - return "public"; + if (arguments.Count == 0) + return "public"; - if (arguments.Count == 1) - { - var argumentExpr = arguments[0].Expression as LiteralExpressionSyntax; - return argumentExpr?.Token.ValueText ?? "public"; - } + if (arguments.Count == 1) + { + var argumentExpr = arguments[0].Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; + } - foreach (var argument in arguments) + foreach (var argument in arguments) + { + // Check if the argument is a named argument + if (argument is AttributeArgumentSyntax attributeArgument) { - // Check if the argument is a named argument - if (argument is AttributeArgumentSyntax attributeArgument) + var nameEquals = attributeArgument.NameEquals; + if (nameEquals?.Name.Identifier.Text == "AccessModifier") { - var nameEquals = attributeArgument.NameEquals; - if (nameEquals?.Name.Identifier.Text == "AccessModifier") - { - var argumentExpr = argument.Expression as LiteralExpressionSyntax; - return argumentExpr?.Token.ValueText ?? "public"; - } + var argumentExpr = argument.Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; } } - - return "public"; } + + return "public"; } - - - - } \ No newline at end of file From b5e5fe77d8cc976b5dcbe6b80a1057270f34189c Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:41:12 +0100 Subject: [PATCH 11/15] refactor: changed the name of the Namespace property --- MapDataReader/GenerateDataReaderMapperAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MapDataReader/GenerateDataReaderMapperAttribute.cs b/MapDataReader/GenerateDataReaderMapperAttribute.cs index 210cd12..ef7ecf0 100644 --- a/MapDataReader/GenerateDataReaderMapperAttribute.cs +++ b/MapDataReader/GenerateDataReaderMapperAttribute.cs @@ -10,7 +10,7 @@ public class GenerateDataReaderMapperAttribute : Attribute /// /// Gets or sets the namespace to be used in the generated methods. /// - public string Namespace { get; set; } + public string NamespaceName { get; set; } public GenerateDataReaderMapperAttribute() { From d080e56dce9a6c0aa614b9188d797f27b6aab964 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:47:08 +0100 Subject: [PATCH 12/15] feat: added the ability to get other properties from the generator attribute --- MapDataReader/MapperGenerator.cs | 53 +++++++++++++++++++------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 3e12b8b..c99b22f 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -23,7 +23,10 @@ public void Execute(GeneratorExecutionContext context) var allProperties = typeNodeSymbol.GetAllSettableProperties(); - var accessModifier = GetAccessModifer(typeNode); + var allAttributes = GetGeneratedMapperAttributes(typeNode); + + var accessModifier = allAttributes["AccessModifier"]; + var namespaceName = allAttributes["NamespaceName"]; var src = $@" // @@ -33,8 +36,8 @@ public void Execute(GeneratorExecutionContext context) using System.Linq; using System.Collections.Generic; //to support List etc - namespace MapDataReader - {{ + namespace {namespaceName}; + public static partial class MapperExtensions {{ {accessModifier} static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value) @@ -105,7 +108,6 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ } src += "\n}"; //end class - src += "\n}"; //end namespace // Add the source code to the compilation context.AddSource($"{typeNodeSymbol.Name}DataReaderMapper.g.cs", src); @@ -117,41 +119,48 @@ public void Initialize(GeneratorInitializationContext context) context.RegisterForSyntaxNotifications(() => new TargetTypeTracker()); } - private string GetAccessModifer(ClassDeclarationSyntax typeNode) + private static Dictionary GetGeneratedMapperAttributes(ClassDeclarationSyntax typeNode) { + // Default attribute values + var defaults = new Dictionary + { + { "AccessModifier", "public" }, + { "NamespaceName", "MapDataReader" } + }; + // Retrieve the attribute list var attributeList = typeNode.AttributeLists .SelectMany(al => al.Attributes) .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper"); if (attributeList?.ArgumentList == null) - return "public"; - + return defaults; + var arguments = attributeList.ArgumentList.Arguments; if (arguments.Count == 0) - return "public"; + return defaults; if (arguments.Count == 1) { - var argumentExpr = arguments[0].Expression as LiteralExpressionSyntax; - return argumentExpr?.Token.ValueText ?? "public"; + var argument = arguments[0]; + if (argument.NameEquals is null) + { + if (argument.Expression is LiteralExpressionSyntax argumentExpr) + defaults["AccessModifier"] = argumentExpr.Token.ValueText; + } } foreach (var argument in arguments) { - // Check if the argument is a named argument - if (argument is AttributeArgumentSyntax attributeArgument) - { - var nameEquals = attributeArgument.NameEquals; - if (nameEquals?.Name.Identifier.Text == "AccessModifier") - { - var argumentExpr = argument.Expression as LiteralExpressionSyntax; - return argumentExpr?.Token.ValueText ?? "public"; - } - } - } + var name = argument.NameEquals?.Name.Identifier.Text; + + if (name == null || !defaults.ContainsKey(name)) continue; - return "public"; + if (argument.Expression is LiteralExpressionSyntax argumentExpr) + defaults[name] = argumentExpr.Token.ValueText; + } + + return defaults; } } \ No newline at end of file From d396190a609782426965004a0c3a40c8b0659142 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:49:21 +0100 Subject: [PATCH 13/15] test: added test for the namespace property --- MapDataReader.Tests/TestGenerator.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/MapDataReader.Tests/TestGenerator.cs b/MapDataReader.Tests/TestGenerator.cs index 8ae70aa..c3a50e9 100644 --- a/MapDataReader.Tests/TestGenerator.cs +++ b/MapDataReader.Tests/TestGenerator.cs @@ -41,7 +41,30 @@ public void TestAccessModifier() namespace MyCode { - [GenerateDataReaderMapper(AccessModifier = ""internal"")] + [GenerateDataReaderMapper(""internal"")] + public class MyClass + { + public string Name {get;set;} + public int Size {get;set;} + public bool Enabled {get;set;} + public System.DateTime Created {get;set;} + public System.DateTimeOffset Offset {get;set;} + public decimal Price {get;set;} + } +} +"; + var src = GetAndCheckOutputSource(userSource); + } + + [TestMethod] + public void TestAttributes() + { + string userSource = @" +using MapDataReader; + +namespace TestNamespace +{ + [GenerateDataReaderMapper(AccessModifier = ""internal"", NamespaceName = ""TestNamespace"")] public class MyClass { public string Name {get;set;} From 8f9421de2980915746e1c3cd679a4870cf0c45fa Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:51:08 +0100 Subject: [PATCH 14/15] feat: added ability to change the name of the generated method --- MapDataReader/GenerateDataReaderMapperAttribute.cs | 5 +++++ MapDataReader/MapperGenerator.cs | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MapDataReader/GenerateDataReaderMapperAttribute.cs b/MapDataReader/GenerateDataReaderMapperAttribute.cs index ef7ecf0..491574b 100644 --- a/MapDataReader/GenerateDataReaderMapperAttribute.cs +++ b/MapDataReader/GenerateDataReaderMapperAttribute.cs @@ -11,6 +11,11 @@ public class GenerateDataReaderMapperAttribute : Attribute /// Gets or sets the namespace to be used in the generated methods. /// public string NamespaceName { get; set; } + + /// + /// Gets or sets the method name to be used in the generated methods. + /// + public string MethodName { get; set; } public GenerateDataReaderMapperAttribute() { diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index c99b22f..cceba2e 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -27,6 +27,7 @@ public void Execute(GeneratorExecutionContext context) var accessModifier = allAttributes["AccessModifier"]; var namespaceName = allAttributes["NamespaceName"]; + var methodName = allAttributes["MethodName"] ?? $"To{typeNode.Identifier}"; var src = $@" // @@ -79,7 +80,7 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ { src += $@" - {accessModifier} static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) + {accessModifier} static List<{typeNodeSymbol.FullName()}> {methodName}(this IDataReader dr) {{ var list = new List<{typeNodeSymbol.FullName()}>(); @@ -125,7 +126,8 @@ private static Dictionary GetGeneratedMapperAttributes(ClassDecla var defaults = new Dictionary { { "AccessModifier", "public" }, - { "NamespaceName", "MapDataReader" } + { "NamespaceName", "MapDataReader" }, + { "MethodName", null } }; // Retrieve the attribute list From 7ee0d41f15e4849a66d9392865956889897ba787 Mon Sep 17 00:00:00 2001 From: Amy Date: Sun, 6 Oct 2024 14:51:37 +0100 Subject: [PATCH 15/15] test: added test for the MethodName attribute --- MapDataReader.Tests/TestGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MapDataReader.Tests/TestGenerator.cs b/MapDataReader.Tests/TestGenerator.cs index c3a50e9..52624a8 100644 --- a/MapDataReader.Tests/TestGenerator.cs +++ b/MapDataReader.Tests/TestGenerator.cs @@ -64,7 +64,7 @@ public void TestAttributes() namespace TestNamespace { - [GenerateDataReaderMapper(AccessModifier = ""internal"", NamespaceName = ""TestNamespace"")] + [GenerateDataReaderMapper(AccessModifier = ""internal"", NamespaceName = ""TestNamespace"", MethodName = ""ConvertToCustom"")] public class MyClass { public string Name {get;set;}