Skip to content
Merged
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Properties related to build/pack -->
<IsPackable>false</IsPackable>
<Version>10.0.12</Version>
<Version>10.0.13-pre01</Version>
<MapsterPluginsTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterPluginsTFMs>
<MapsterTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterTFMs>
<MapsterEFCoreTFMs>net10.0;net9.0;net8.0</MapsterEFCoreTFMs>
Expand Down
97 changes: 96 additions & 1 deletion src/ExpressionTranslator/ExpressionTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml.Linq;

namespace ExpressionDebugger
{
Expand Down Expand Up @@ -1268,6 +1269,88 @@ public Expression VisitLambda(LambdaExpression node, LambdaType type, string? me
}
}

public Expression VisitLambdaForGenerateMappers(LambdaExpression node, LambdaType type, Type InterfaceType, string? methodName = null,
bool isInternal = false)
{
VisitLambda(node, type, methodName, isInternal);

if (!isInternal)
isInternal = node.ReturnType.GetTypeInfo().IsNotPublic ||
node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic);

if(!isInternal)
return node; // skip create interface implimentation if public only

if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda)
{
_inlineCount++;
if (type == LambdaType.PublicLambda)
{
var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main";
WriteLine();
var funcType = MakeDelegateType(node.ReturnType, node.Parameters.Select(it => it.Type).ToArray());
var exprType = typeof(Expression<>).MakeGenericType(funcType);
Write(Translate(exprType), " ", name, " => ");
}

IList<ParameterExpression> args;
if (node.Parameters.Count == 1)
{
args = new List<ParameterExpression>();
var arg = VisitParameter(node.Parameters[0]);
args.Add((ParameterExpression)arg);
}
else
{
args = VisitArguments("(", node.Parameters.ToList(), p => (ParameterExpression)VisitParameter(p),
")");
}

Write(" => ");
var body = VisitGroup(node.Body, ExpressionType.Quote);
if (type == LambdaType.PublicLambda)
Write(";");
_inlineCount--;
return Expression.Lambda(body, node.Name, node.TailCall, args);
}
else
{
var name = methodName != null ? $"{InterfaceType.FullName}.{methodName}" : "Main";
if (type == LambdaType.PublicMethod || type == LambdaType.ExtensionMethod)
{
if (!isInternal)
isInternal = node.ReturnType.GetTypeInfo().IsNotPublic ||
node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic);
WriteLine();
Methods[name] = node.Type;
}
else
{
name = GetName(node, name);
WriteModifierNextLine("private");
}

Write(Translate(node.ReturnType), " ", name);
var open = "(";
if (type == LambdaType.ExtensionMethod)
{
if (Definitions?.IsStatic != true)
throw new InvalidOperationException("Extension method requires static class");
if (node.Parameters.Count == 0)
throw new InvalidOperationException("Extension method requires at least 1 parameter");
open = "(this ";
}

var args = VisitArguments(open, node.Parameters, VisitParameterDeclaration, ")");
Indent();
var body = VisitBody(node.Body, true);

Outdent();

return Expression.Lambda(body, name, node.TailCall, args);
}
}

private HashSet<LambdaExpression>? _visitedLambda;
private int _writerLevel;

Expand Down Expand Up @@ -1865,9 +1948,16 @@ public override string ToString()
WriteNextLine("using ", ns, ";");
}

WriteLine();
}

foreach (var ns in Definitions.GeneratedAttributes.Select(x => x.NameSpace).Distinct())
{
WriteNextLine("using ", ns, ";");
}

if(_usings != null || Definitions.GeneratedAttributes.Count != 0)
WriteLine();

// NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName
// keep logic here for compatibility
if (_typeNames != null)
Expand All @@ -1891,6 +1981,11 @@ public override string ToString()
Indent();
}

foreach (var gAttr in Definitions.GeneratedAttributes)
{
WriteNextLine(gAttr.Implimentation);
}

var isInternal = Definitions.IsInternal;
if (!isInternal)
isInternal = Definitions.Implements?.Any(it =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace ExpressionDebugger.Helpers.GeneratedAttributes
{
public interface IGeneratedAttribute
{
public string NameSpace { get;}
public string Declaration { get;}
public string Implimentation { get; }
public string FileName { get;}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Text;
using static ExpressionDebugger.Helpers.RandomNamespaceGenerator;

namespace ExpressionDebugger.Helpers.GeneratedAttributes
{
public class MapsterToolGeneratedMapperAttribute : GeneratedBase, IGeneratedAttribute
{
private readonly StringBuilder _Declaration;
private readonly string _NameSpace;

public string NameSpace => _NameSpace;

public string Declaration => _Declaration.ToString();

public string Implimentation => "[MapsterToolGeneratedMapper]";

public string FileName => "MapsterToolGeneratedMapperAttribute";

public MapsterToolGeneratedMapperAttribute(string extendedNameSpace)
{
if (String.IsNullOrEmpty(extendedNameSpace))
throw new ArgumentNullException("Extended namespace not specified or is null/empty string");

if(CheckNameSpace.IsMatch(extendedNameSpace))
_NameSpace = $"Mapster.Generated.Attributes.{extendedNameSpace}";
else
_NameSpace = $"Mapster.Generated.Attributes.{Generate(extendedNameSpace,1,1)}";

_Declaration = new StringBuilder();

_Declaration.Append("using System;\r\n\r\n");
_Declaration.Append($"namespace {NameSpace}");
_Declaration.Append("\r\n{\r\n public sealed class MapsterToolGeneratedMapperAttribute : Attribute\r\n {\r\n\r\n }\r\n} ");
}

}
}
18 changes: 18 additions & 0 deletions src/ExpressionTranslator/Helpers/GeneratedBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace ExpressionDebugger.Helpers
{
public abstract class GeneratedBase
{
public override bool Equals(object obj)
{
if(obj is null)
return base.Equals(obj);
else
return this.GetType() == obj.GetType();
}

public override int GetHashCode()
{
return this.GetType().GetHashCode();
}
}
}
33 changes: 33 additions & 0 deletions src/ExpressionTranslator/Helpers/MemberInfoExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Reflection;

namespace ExpressionDebugger.Helpers
{
public static class MemberInfoExtensions
{
public static bool IsPublicOrInternal(this MethodInfo method)
{
if (method == null) throw new ArgumentNullException(nameof(method));

return !method.IsPrivate
&& !method.IsFamily
&& !method.IsFamilyOrAssembly
&& !method.IsFamilyAndAssembly
&& (method.IsPublic || true);
}



public static bool IsGetterPublicOrInternal(this PropertyInfo property)
{
if (property == null) throw new ArgumentNullException(nameof(property));

MethodInfo? getMethod = property.GetMethod;

if (getMethod == null) return false;

return getMethod.IsPublicOrInternal();
}
}

}
80 changes: 80 additions & 0 deletions src/ExpressionTranslator/Helpers/RandomNamespaceGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace ExpressionDebugger.Helpers
{
public static class RandomNamespaceGenerator
{
public static readonly Regex CheckNameSpace = new Regex(@"^([a-zA-Z_]\w*)(\.[a-zA-Z_]\w*)*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private const string Consonants = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
private const string Vowels = "aeiouAEIOU";
private const string Digits = "0123456789";

public static string Generate(string input, int minParts = 2, int maxParts = 4)
{
if (string.IsNullOrEmpty(input)) throw new ArgumentException("Input cannot be empty.");
if (minParts < 1) minParts = 1;
if (maxParts < minParts) maxParts = minParts;

using var sha256 = SHA256.Create();
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));

long seed = BitConverter.ToInt64(hashBytes, 0);
var random = new Random(unchecked((int)seed ^ (int)(seed >> 32)));

int partsCount = random.Next(minParts, maxParts + 1);
var sb = new StringBuilder();

for (int i = 0; i < partsCount; i++)
{
if (i > 0) sb.Append('.');
sb.Append(GeneratePart(random));
}

return sb.ToString();
}


public static string Generate(int minParts = 2, int maxParts = 4)
{
if (minParts < 1) minParts = 1;
if (maxParts < minParts) maxParts = minParts;

var _random = new Random();

int partsCount = _random.Next(minParts, maxParts + 1);
var sb = new StringBuilder();

for (int i = 0; i < partsCount; i++)
{
if (i > 0) sb.Append('.');
sb.Append(GeneratePart(_random));
}

return sb.ToString();
}

private static string GeneratePart(Random random, int minLength = 2, int maxLength = 10)
{
if (minLength < 1) minLength = 1;
if (maxLength < minLength) maxLength = minLength;

int length = random.Next(minLength, maxLength + 1);
var sb = new StringBuilder(length);

sb.Append(Consonants[random.Next(Consonants.Length)]);

for (int i = 1; i < length; i++)
{
string pool = (i % 2 == 0) ? Vowels : Consonants;
if (random.NextDouble() < 0.1) pool = Digits;
sb.Append(pool[random.Next(pool.Length)]);
}

return sb.ToString();
}
}
}

4 changes: 3 additions & 1 deletion src/ExpressionTranslator/TypeDefinitions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using ExpressionDebugger.Helpers.GeneratedAttributes;
using System;
using System.Collections.Generic;

namespace ExpressionDebugger
Expand All @@ -12,6 +13,7 @@ public class TypeDefinitions
public IEnumerable<Type>? Implements { get; set; }
public bool PrintFullTypeName { get; set; }
public bool IsRecordType { get; set; }
public HashSet<IGeneratedAttribute> GeneratedAttributes { get; set; } = new HashSet<IGeneratedAttribute>();

/// <summary>
/// Set to 2 to mark all properties as nullable
Expand Down
7 changes: 7 additions & 0 deletions src/Mapster.Tool/MapperOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ public class MapperOptions
[Option('N', "nullableDirective", Required = false, HelpText = "Set true to add \"#nullable enable\" to the top of generated mapper files")]
public bool GenerateNullableDirective { get; set; }

[Option('h', "helpersCreate", Required = false, HelpText = "Generate helpers features")]
public bool CreateHelpers { get; set; }

[Option('H', "helpersNamespace", Required = false, HelpText = "Specify additional namespace to generated helpers features")]
public string? HelpersNamespace { get; set; }


[Usage(ApplicationAlias = "dotnet mapster mapper")]
public static IEnumerable<Example> Examples =>
new List<Example>
Expand Down
Loading
Loading