diff --git a/src/Benchmark/Benchmark.csproj b/src/Benchmark/Benchmark.csproj index 64bf338a..739dc721 100644 --- a/src/Benchmark/Benchmark.csproj +++ b/src/Benchmark/Benchmark.csproj @@ -19,14 +19,15 @@ - + - - + + + diff --git a/src/Benchmark/Benchmarks/TestAll.cs b/src/Benchmark/Benchmarks/TestAll.cs index 4b641334..40388f24 100644 --- a/src/Benchmark/Benchmarks/TestAll.cs +++ b/src/Benchmark/Benchmarks/TestAll.cs @@ -83,10 +83,10 @@ public void SetupFec() [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - FooMapper.Map(_fooInstance); - CustomerMapper.Map(_customerInstance); + //_fooInstance = TestAdaptHelper.SetupFooInstance(); + //_customerInstance = TestAdaptHelper.SetupCustomerInstance(); + //FooMapper.Map(_fooInstance); + //CustomerMapper.Map(_customerInstance); } [GlobalSetup(Target = nameof(ExpressMapperTest))] diff --git a/src/Benchmark/Benchmarks/TestComplexTypes.cs b/src/Benchmark/Benchmarks/TestComplexTypes.cs index 048128b8..971f2e73 100644 --- a/src/Benchmark/Benchmarks/TestComplexTypes.cs +++ b/src/Benchmark/Benchmarks/TestComplexTypes.cs @@ -70,8 +70,8 @@ public void SetupFec() [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - CustomerMapper.Map(_customerInstance); + //_customerInstance = TestAdaptHelper.SetupCustomerInstance(); + //CustomerMapper.Map(_customerInstance); } [GlobalSetup(Target = nameof(ExpressMapperTest))] diff --git a/src/Benchmark/Benchmarks/TestSimpleTypes.cs b/src/Benchmark/Benchmarks/TestSimpleTypes.cs index bc96def0..7c832a56 100644 --- a/src/Benchmark/Benchmarks/TestSimpleTypes.cs +++ b/src/Benchmark/Benchmarks/TestSimpleTypes.cs @@ -71,8 +71,8 @@ public void SetupFec() [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - FooMapper.Map(_fooInstance); + //_fooInstance = TestAdaptHelper.SetupFooInstance(); + //FooMapper.Map(_fooInstance); } [GlobalSetup(Target = nameof(ExpressMapperTest))] diff --git a/src/Benchmark/TestAdaptHelper.cs b/src/Benchmark/TestAdaptHelper.cs index 107915c6..3d7db0eb 100644 --- a/src/Benchmark/TestAdaptHelper.cs +++ b/src/Benchmark/TestAdaptHelper.cs @@ -88,7 +88,7 @@ public static void ConfigureMapster(Foo fooInstance, MapsterCompilerType type) } public static void ConfigureExpressMapper(Foo fooInstance) { - ExpressMapper.Mapper.Map(fooInstance); //exercise + //ExpressMapper.Mapper.Map(fooInstance); //exercise } public static void ConfigureAutoMapper(Foo fooInstance) { @@ -103,7 +103,7 @@ public static void ConfigureMapster(Customer customerInstance, MapsterCompilerTy } public static void ConfigureExpressMapper(Customer customerInstance) { - ExpressMapper.Mapper.Map(customerInstance); //exercise + //ExpressMapper.Mapper.Map(customerInstance); //exercise } public static void ConfigureAutoMapper(Customer customerInstance) { @@ -121,7 +121,7 @@ public static void TestExpressMapper(TSrc item, int iterations) where TSrc : class where TDest : class, new() { - Loop(item, get => ExpressMapper.Mapper.Map(get), iterations); + //Loop(item, get => ExpressMapper.Mapper.Map(get), iterations); } public static void TestAutoMapper(TSrc item, int iterations) @@ -133,12 +133,12 @@ public static void TestAutoMapper(TSrc item, int iterations) public static void TestCodeGen(Foo item, int iterations) { - Loop(item, get => FooMapper.Map(get), iterations); + //Loop(item, get => FooMapper.Map(get), iterations); } public static void TestCodeGen(Customer item, int iterations) { - Loop(item, get => CustomerMapper.Map(get), iterations); + //Loop(item, get => CustomerMapper.Map(get), iterations); } private static void Loop(T item, Action action, int iterations) diff --git a/src/ExpressionDebugger.Console/ExpressionDebugger.Console.csproj b/src/ExpressionDebugger.Console/ExpressionDebugger.Console.csproj new file mode 100644 index 00000000..e7fd22a3 --- /dev/null +++ b/src/ExpressionDebugger.Console/ExpressionDebugger.Console.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + true + + + + + + + + + diff --git a/src/ExpressionDebugger.Console/Program.cs b/src/ExpressionDebugger.Console/Program.cs new file mode 100644 index 00000000..23268676 --- /dev/null +++ b/src/ExpressionDebugger.Console/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq.Expressions; + +namespace ExpressionDebugger.Console +{ + class Program + { + static void Main(string[] args) + { + var p1 = Expression.Parameter(typeof(int)); + var p2 = Expression.Parameter(typeof(int)); + var body = Expression.Add(p1, Expression.Block( + new Expression[] { + Expression.Call(typeof(System.Console).GetMethod("WriteLine", new [] { typeof(int) }), p2), + p2, + })); + var lambda = Expression.Lambda>(body, p1, p2); + + var script = lambda.ToScript(); + + var fun = lambda.CompileWithDebugInfo(); + var result = fun(1, 2); + System.Console.WriteLine(result); + } + } +} diff --git a/src/ExpressionDebugger.Tests/DebugInfoInjectorTest.cs b/src/ExpressionDebugger.Tests/DebugInfoInjectorTest.cs new file mode 100644 index 00000000..a18711b7 --- /dev/null +++ b/src/ExpressionDebugger.Tests/DebugInfoInjectorTest.cs @@ -0,0 +1,821 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ExpressionDebugger.Tests +{ + [TestClass] + public class DebugInfoInjectorTest + { + [TestMethod] + public void TestBinary() + { + Expression> fn = (a, b) => a + b; + var str = fn.ToScript(); + + var expected = @" +public int Main(int a, int b) +{ + return a + b; +}".Trim(); + + // Well, in Visual Studio (at bottom of the code editor, you should be sure to have "CRLF" for the DebugInfoInjectorTest.cs file) + // if not, you may have problem comparing \r\n with \n. + var r = str.Equals(expected); + + Assert.AreEqual(expected, str); + } + + [TestMethod] + public void TestBinary_PowerAssign() + { + var exp = Expression.PowerAssign(Expression.Variable(typeof(double), "d"), Expression.Constant(2d)); + var str = exp.ToScript(); + Assert.AreEqual("d = Math.Pow(d, 2d)", str); + } + + [TestMethod] + public void TestBinary_ArrayIndex() + { + Expression> fn = a => a[0]; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int[] a) +{ + return a[0]; +}".Trim() + , str); + } + + [TestMethod] + public void TestBlock() + { + var p1 = Expression.Variable(typeof(int)); + var block = Expression.Block(new[] { p1 }, Expression.Add(p1, p1)); + var str = block.ToScript(); + Assert.AreEqual(@" +int p1; + +p1 + p1;".Trim(), str); + } + + [TestMethod] + public void Test_Conditional() + { + Expression> fn = a => a < 0 ? a - 1 : a + 1; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int a) +{ + return a < 0 ? a - 1 : a + 1; +}".Trim() + , str); + } + + [TestMethod] + public void TestConditional_Block() + { + var exp = Expression.Condition( + Expression.Equal(Expression.Constant(1), Expression.Constant(2)), + Expression.Constant(4), + Expression.Constant(3), + typeof(void)); + var str = exp.ToScript(); + Assert.AreEqual(@" +if (1 == 2) +{ + 4; +} +else +{ + 3; +}".Trim() + , str); + } + + [TestMethod] + public void TestConditional_Block_Chain() + { + var exp = Expression.Condition( + Expression.Equal(Expression.Constant(1), Expression.Constant(2)), + Expression.Constant(4), + Expression.Condition( + Expression.Equal(Expression.Constant(5), Expression.Constant(6)), + Expression.Constant(3), + Expression.Constant(2), + typeof(void)), + typeof(void)); + var str = exp.ToScript(); + Assert.AreEqual(@" +if (1 == 2) +{ + 4; +} +else if (5 == 6) +{ + 3; +} +else +{ + 2; +}".Trim() + , str); + } + + [TestMethod] + public void TestConstants() + { + Expression> fn = s => s == "x" || s == @"\" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0]; + var str = fn.ToScript(); + Assert.AreEqual(@" +public char Main(string s) +{ + return s == ""x"" || s == @""\"" || s == null || s.IsNormalized() == false || s.GetType() == typeof(string) ? 'x' : s[0]; +}".Trim() + , str); + + Expression> fn2 = () => 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString(); + var str2 = fn2.ToScript(); + Assert.AreEqual(@" +public string Main() +{ + return 1f.ToString() + 2m.ToString() + ((byte)1).ToString() + DayOfWeek.Friday.ToString() + default(DateTime).ToString(); +}".Trim() + , str2); + } + + [TestMethod] + public void TestConstant_DateTime() + { + var now = DateTime.Now; + var expr = Expression.Constant(now); + var script = expr.ToScript(); + Assert.AreEqual(@" +private DateTime DateTime1; +DateTime1".Trim(), script); + } + +// [TestMethod] +// public void TestDynamic() +// { +// var dynType = typeof(ExpandoObject); +// var p1 = Expression.Variable(dynType); +// var line1 = Expression.Dynamic(Binder.Convert(CSharpBinderFlags.None, typeof(Poco), dynType), typeof(object), p1); +// var line2 = Expression.Dynamic(Binder.GetMember(CSharpBinderFlags.None, "Blah", dynType, +// new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }), typeof(object), p1); +// var line3 = Expression.Dynamic(Binder.SetMember(CSharpBinderFlags.None, "Blah", dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(0)); +// var line4 = Expression.Dynamic(Binder.GetIndex(CSharpBinderFlags.None, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(1)); +// var line5 = Expression.Dynamic(Binder.SetIndex(CSharpBinderFlags.None, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(1), Expression.Constant(0)); +// var line6 = Expression.Dynamic(Binder.InvokeMember(CSharpBinderFlags.None, "Blah", null, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(2)); +// var line7 = Expression.Dynamic(Binder.Invoke(CSharpBinderFlags.None, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(2)); +// var line8 = Expression.Dynamic(Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.Negate, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1); +// var line9 = Expression.Dynamic(Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, dynType, +// new[] +// { +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), +// }), typeof(object), p1, Expression.Constant(3)); +// var expr = Expression.Block(new[] { p1 }, line1, line2, line3, line4, line5, line6, line7, line8, line9); +// var str = expr.ToScript(); +// Assert.AreEqual(@" +//dynamic p1; + +//(Poco)p1; +//p1.Blah; +//p1.Blah = 0; +//p1[1]; +//p1[1] = 0; +//p1.Blah(2); +//p1(2); +//-p1; +//p1 + 3;" +// , str); +// } + + [TestMethod] + public void TestDefault() + { + var exp = Expression.Default(typeof(int)); + var str = exp.ToScript(); + Assert.AreEqual("default(int)", str); + } + + [TestMethod] + public void TestGroup() + { + Expression> fn = x => -(-x) + 1 + x - (1 - x); + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int x) +{ + return -(-x) + 1 + x - (1 - x); +}".Trim() + , str); + } + + [TestMethod] + public void TestGroup_MultiLine() + { + var p = Expression.Variable(typeof(int), "p"); + var exp = Expression.Add( + p, + Expression.Block( + new Expression[] { + Expression.Call(typeof(Console).GetMethod(nameof(Console.WriteLine), new [] { typeof(int) }), p), + p, + } + )); + var str = exp.ToScript(); + Assert.AreEqual(@"p + (new Func(() => { + Console.WriteLine(p); + return p; +}))()" + , str); + } + + [TestMethod] + public void TestIndex() + { + var p1 = Expression.Parameter(typeof(int[])); + var expr = Expression.MakeIndex(p1, null, new[] { Expression.Constant(1) }); + var str = expr.ToScript(); + Assert.AreEqual("p1[1]", str); + } + + [TestMethod] + public void TestLambda() + { + var p1 = Expression.Parameter(typeof(int)); + var func1 = Expression.Lambda( + Expression.Increment(p1), + p1); + var expr = Expression.Block( + Expression.Add( + Expression.Invoke(func1, Expression.Constant(1)), + Expression.Invoke(func1, Expression.Constant(1)))); + var str = expr.ToScript(); + Assert.AreEqual(@" +func1(1) + func1(1); + +private int func1(int p1) +{ + return p1 + 1; +}".Trim() + , str); + } + + [TestMethod] + public void TestLambda_Inline() + { + Expression, IQueryable>> fn = q => q.Where(it => it > 0); + var str = fn.ToScript(); + Assert.AreEqual(@" +public IQueryable Main(IQueryable q) +{ + return q.Where(it => it > 0); +}".Trim() + , str); + } + + [TestMethod] + public void TestListInit() + { + Expression>> list = () => new List() { 1, 2, 3 }; + var str = list.ToScript(); + Assert.AreEqual(@" +public List Main() +{ + return new List() {1, 2, 3}; +}".Trim() + , str); + } + + [TestMethod] + public void TestListInit_Dictionary() + { + Expression>> list = () => new Dictionary() + { + {1, 2}, + {3, 4} + }; + var str = list.ToScript(); + Assert.AreEqual(@" +public Dictionary Main() +{ + return new Dictionary() {{1, 2}, {3, 4}}; +}".Trim() + , str); + } + + [TestMethod] + public void TestLoop() + { + var @break = Expression.Label(); + var @continue = Expression.Label(); + var @return = Expression.Label(); + var p1 = Expression.Parameter(typeof(int)); + var expr = Expression.Loop( + Expression.Condition( + Expression.GreaterThanOrEqual(p1, Expression.Constant(1)), + Expression.Condition( + Expression.Equal(p1, Expression.Constant(1)), + Expression.Return(@return, p1), + Expression.Block( + Expression.PostDecrementAssign(p1), + Expression.Continue(@continue))), + Expression.Break(@break)), + @break, + @continue); + + var str = expr.ToScript(); + Assert.AreEqual(@" +while (p1 >= 1) +{ + if (p1 == 1) + { + return p1; + } + else + { + p1--; + continue; + } +}".Trim() + , str); + } + + [TestMethod] + public void TestLoop2() + { + var label = Expression.Label(); + var expr = Expression.Block( + Expression.Loop(Expression.Goto(label)), + Expression.Label(label)); + var str = expr.ToScript(); + Assert.AreEqual(@" +while (true) +{ + goto label1; +} +label1:".Trim() + , str); + } + + [TestMethod] + public void TestMemberAccess() + { + Expression> fn = () => DateTime.Now.Day; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main() +{ + return DateTime.Now.Day; +}".Trim() + , str); + } + + private class Poco + { + public string Name { get; set; } + public Poco Parent { get; } = new Poco(); + public List Children { get; } = new List(); + } + + [TestMethod] + public void TestMemberInit() + { + Expression> fn = () => new Poco() + { + Name = "1", + Parent = { Name = "2" }, + Children = { new Poco(), new Poco() } + }; + var str = fn.ToScript(); + Assert.AreEqual(@" +public DebugInfoInjectorTest.Poco Main() +{ + return new DebugInfoInjectorTest.Poco() + { + Name = ""1"", + Parent = {Name = ""2""}, + Children = {new DebugInfoInjectorTest.Poco(), new DebugInfoInjectorTest.Poco()} + }; +}".Trim() + , str); + } + + [TestMethod] + public void TestMethodCall_Default() + { + Expression, int>> fn = dict => dict[0]; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(Dictionary dict) +{ + return dict[0]; +}".Trim() + , str); + } + + [TestMethod] + public void TestMethodCall() + { + Expression, string>> fn = list => list.ToString(); + var str = fn.ToScript(); + Assert.AreEqual(@" +public string Main(List list) +{ + return list.ToString(); +}".Trim() + , str); + } + + [TestMethod] + public void TestMethodCall_Static() + { + Expression> fn = (a, b) => Math.Max(a, b); + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int a, int b) +{ + return Math.Max(a, b); +}".Trim() + , str); + } + + [TestMethod] + public void TestNewArray() + { + Expression> fn = i => new[] { i }; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int[] Main(int i) +{ + return new int[] {i}; +}".Trim() + , str); + } + + [TestMethod] + public void TestNewArray_Bound() + { + Expression> fn = i => new int[i]; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int[] Main(int i) +{ + return new int[i]; +}".Trim() + , str); + } + + [TestMethod] + public void TestParameter_Reserved() + { + Expression> fn = @null => @null.Value; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int? @null) +{ + return @null.Value; +}".Trim() + , str); + } + + [TestMethod] + public void TestSwitch() + { + var p1 = Expression.Parameter(typeof(int)); + var expr = Expression.Switch( + p1, + Expression.Constant(0), + Expression.SwitchCase(Expression.Constant(1), Expression.Constant(1))); + var str = expr.ToScript(); + Assert.AreEqual(@" +switch (p1) +{ + case 1: + 1; + break; + default: + 0; + break; +}".Trim() + , str); + } + + [TestMethod] + public void TestTryCatch() + { + var p1 = Expression.Parameter(typeof(double)); + var tryCatch = Expression.TryCatchFinally( + Expression.Block( + typeof(void), + Expression.Assign( + p1, + Expression.Divide(Expression.Constant(1.0), Expression.Constant(0.0)))), + Expression.Throw(Expression.New(typeof(NotSupportedException))), + Expression.Catch( + Expression.Parameter(typeof(DivideByZeroException)), + Expression.Rethrow(), + Expression.Constant(true))); + var str = tryCatch.ToScript(); + Assert.AreEqual(@" +try +{ + p1 = 1d / 0d; +} +catch (DivideByZeroException p2) when (true) +{ + throw; +} +finally +{ + throw new NotSupportedException(); +}".Trim() + , str); + } + + [TestMethod] + public void TestTryFault() + { + var expr = Expression.TryFault( + Expression.Constant(1), + Expression.Constant("blah")); + var str = expr.ToScript(); + Assert.AreEqual(@" +bool fault1 = true; +try +{ + 1; + fault1 = false; +} +finally +{ + if (fault1) + { + ""blah""; + } +}".Trim() + , str); + } + + [TestMethod] + public void TestTypeBinary() + { + Expression> fn = o => o is Array; + var str = fn.ToScript(); + Assert.AreEqual(@" +public bool Main(object o) +{ + return o is Array; +}".Trim() + , str); + } + + [TestMethod] + public void TestUnary_Convert() + { + Expression> fn = d => (int)d; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(double d) +{ + return (int)d; +}".Trim() + , str); + } + + [TestMethod] + public void TestUnary_ArrayLength() + { + Expression> fn = a => a.Length; + var str = fn.ToScript(); + Assert.AreEqual(@" +public int Main(int[] a) +{ + return a.Length; +}".Trim() + , str); + } + + [TestMethod] + public void TestUnary_As() + { + Expression> fn = expr => expr as UnaryExpression; + var str = fn.ToScript(); + Assert.AreEqual(@" +public Expression Main(Expression expr) +{ + return expr as UnaryExpression; +}".Trim() + , str); + } + + internal static int GetInternal() => 1; + + [TestMethod] + public void TestToString() + { + var call = Expression.Call( + typeof(DebugInfoInjectorTest).GetMethod(nameof(GetInternal), + BindingFlags.Static | BindingFlags.NonPublic) + ); + var exp = Expression.Lambda>(call); + var str = exp.ToScript(new ExpressionDefinitions + { + IsStatic = true, + MethodName = "Main", + Namespace = "ExpressionDebugger.Tests", + TypeName = "MockClass" + }); + Assert.AreEqual(@" +using System; + +namespace ExpressionDebugger.Tests +{ + public static partial class MockClass + { + private static Func GetInternal1; + + public static int Main() + { + return GetInternal1.Invoke(); + } + } +}".Trim() + , str); + } + + [TestMethod] + public void TestExpression() + { + Expression> lambda = data => new Data {Id = data.Id + "1", Records = data.Records.Select(it => it + 1)}; + var str = lambda.ToScript(new ExpressionDefinitions {IsExpression = true}); + Assert.AreEqual(@" +public Expression> Main => data => new DebugInfoInjectorTest.Data() +{ + Id = data.Id + ""1"", + Records = data.Records.Select(it => it + 1) +};".Trim(), str); + } + + [TestMethod] + public void TestExtensionMethod() + { + var p1 = Expression.Parameter(typeof(int)); + var p2 = Expression.Parameter(typeof(int)); + var lambda = Expression.Lambda>( + Expression.Add(p1, p2), + p1, p2); + var translator = new ExpressionTranslator(new TypeDefinitions + { + IsStatic = true, + Namespace = "ExpressionDebugger.Tests", + TypeName = "MockClass" + }); + translator.VisitLambda(lambda, ExpressionTranslator.LambdaType.ExtensionMethod, "Add"); + var str = translator.ToString(); + Assert.AreEqual(@" +namespace ExpressionDebugger.Tests +{ + public static partial class MockClass + { + public static int Add(this int p1, int p2) + { + return p1 + p2; + } + } +}".Trim(), str); + } + + [TestMethod] + public void TestProperties() + { + var translator = new ExpressionTranslator(new TypeDefinitions + { + IsStatic = false, + Namespace = "ExpressionDebugger.Tests", + TypeName = "MockClass", + NullableContext = 2, + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop1", + Type = typeof(string), + NullableContext = 0, + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop2", + Type = typeof(List>), + IsReadOnly = true, + Nullable = new byte[] { 1, 2, 1, 2 } + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop3", + Type = typeof(string), + IsInitOnly = true, + NullableContext = 0, + }); + var str = translator.ToString(); + Assert.AreEqual(@" +using System.Collections.Generic; + +namespace ExpressionDebugger.Tests +{ + public partial class MockClass + { + public string Prop1 { get; set; } + public List?> Prop2 { get; } + public string Prop3 { get; init; } + + public MockClass(List?> prop2) + { + this.Prop2 = prop2; + } + } +}".Trim(), str); + } + + + [TestMethod] + public void TestRecordType() + { + var translator = new ExpressionTranslator(new TypeDefinitions + { + IsStatic = false, + Namespace = "ExpressionDebugger.Tests", + TypeName = "MockClass", + IsRecordType = true, + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop1", + Type = typeof(string) + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop2", + Type = typeof(string), + IsReadOnly = true + }); + translator.Properties.Add(new PropertyDefinitions + { + Name = "Prop3", + Type = typeof(string), + IsInitOnly = true + }); + var str = translator.ToString(); + Assert.AreEqual(@" +namespace ExpressionDebugger.Tests +{ + public partial record MockClass(string prop2) + { + public string Prop1 { get; set; } + public string Prop3 { get; init; } + } +}".Trim(), str); + } + + public class Data + { + public string Id { get; set; } + public IEnumerable Records { get; set; } + } + } +} diff --git a/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj b/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj new file mode 100644 index 00000000..657fc325 --- /dev/null +++ b/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + + false + + + + + + + + + + + + diff --git a/src/ExpressionDebugger.sln b/src/ExpressionDebugger.sln new file mode 100644 index 00000000..6b4b8d45 --- /dev/null +++ b/src/ExpressionDebugger.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger", "ExpressionDebugger\ExpressionDebugger.csproj", "{A9B56FC4-B2BC-4A32-B1C0-B926F6259666}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Tests", "ExpressionDebugger.Tests\ExpressionDebugger.Tests.csproj", "{024FB14F-7B40-42E4-8B80-1D348914124C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionDebugger.Console", "ExpressionDebugger.Console\ExpressionDebugger.Console.csproj", "{71E2EC60-6780-4AB8-9773-91B4939560FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionTranslator", "ExpressionTranslator\ExpressionTranslator.csproj", "{9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9B56FC4-B2BC-4A32-B1C0-B926F6259666}.Release|Any CPU.Build.0 = Release|Any CPU + {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {024FB14F-7B40-42E4-8B80-1D348914124C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {024FB14F-7B40-42E4-8B80-1D348914124C}.Release|Any CPU.Build.0 = Release|Any CPU + {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71E2EC60-6780-4AB8-9773-91B4939560FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71E2EC60-6780-4AB8-9773-91B4939560FB}.Release|Any CPU.Build.0 = Release|Any CPU + {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B67882F-BE07-45C9-9B86-FB33A8B4EA5B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ExpressionDebugger/ExpressionCompilationOptions.cs b/src/ExpressionDebugger/ExpressionCompilationOptions.cs new file mode 100644 index 00000000..6bab44fc --- /dev/null +++ b/src/ExpressionDebugger/ExpressionCompilationOptions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace ExpressionDebugger +{ + public class ExpressionCompilationOptions + { + public ExpressionDefinitions? DefaultDefinitions { get; set; } + public IEnumerable? References { get; set; } + public bool EmitFile { get; set; } + public string? RootPath { get; set; } + public bool? IsRelease { get; set; } + public bool ThrowOnFailedCompilation { get; set; } + } +} diff --git a/src/ExpressionDebugger/ExpressionCompiler.cs b/src/ExpressionDebugger/ExpressionCompiler.cs new file mode 100644 index 00000000..2f9203de --- /dev/null +++ b/src/ExpressionDebugger/ExpressionCompiler.cs @@ -0,0 +1,136 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace ExpressionDebugger +{ + public class ExpressionCompiler + { + public List Translators { get; } = new List(); + + private readonly ExpressionCompilationOptions? _options; + public ExpressionCompiler(ExpressionCompilationOptions? options = null) + { + _options = options; + } + + private readonly List _codes = new List(); + public void AddFile(string code, string filename) + { + var buffer = Encoding.UTF8.GetBytes(code); + + var path = filename; + if (_options?.EmitFile == true) + { + var root = _options?.RootPath + ?? Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "GeneratedSources"); + Directory.CreateDirectory(root); + path = Path.Combine(root, filename); + using var fs = new FileStream(path, FileMode.Create); + fs.Write(buffer, 0, buffer.Length); + } + + var sourceText = SourceText.From(buffer, buffer.Length, Encoding.UTF8, canBeEmbedded: true); + + var syntaxTree = CSharpSyntaxTree.ParseText( + sourceText, + new CSharpParseOptions(), + path); + + var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode; + var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, path, Encoding.UTF8); + _codes.Add(encoded); + } + + public void AddFile(LambdaExpression node, ExpressionDefinitions? definitions = null) + { + definitions ??= _options?.DefaultDefinitions ?? new ExpressionDefinitions {IsStatic = true}; + definitions.TypeName ??= "Program"; + + var translator = ExpressionTranslator.Create(node, definitions); + var script = translator.ToString(); + Translators.Add(translator); + + this.AddFile(script, Path.ChangeExtension(Path.GetRandomFileName(), ".cs")); + } + + public Assembly CreateAssembly() + { + var references = new HashSet(); + references.UnionWith(from t in Translators + from n in t.TypeNames + select n.Key.Assembly); + + if (_options?.References != null) + references.UnionWith(_options.References); + references.Add(typeof(object).Assembly); + +#if NETSTANDARD2_0 || NET6_0_OR_GREATER + references.Add(Assembly.Load(new AssemblyName("netstandard"))); + references.Add(Assembly.Load(new AssemblyName("System.Runtime"))); + references.Add(Assembly.Load(new AssemblyName("System.Collections"))); +#endif + + var assemblyName = Path.GetRandomFileName(); + var symbolsName = Path.ChangeExtension(assemblyName, "pdb"); + + var metadataReferences = references.Select(it => MetadataReference.CreateFromFile(it.Location)); + var isRelease = _options?.IsRelease ?? !Debugger.IsAttached; + CSharpCompilation compilation = CSharpCompilation.Create( + assemblyName, + _codes, + metadataReferences, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, usings: new[] { "System" }) + .WithOptimizationLevel(isRelease ? OptimizationLevel.Release : OptimizationLevel.Debug) + .WithPlatform(Platform.AnyCpu) + ); + + using var assemblyStream = new MemoryStream(); + using var symbolsStream = new MemoryStream(); + var emitOptions = new EmitOptions( + debugInformationFormat: DebugInformationFormat.PortablePdb, + pdbFilePath: symbolsName); + + var embeddedTexts = _codes.Select(it => EmbeddedText.FromSource(it.FilePath, it.GetText())); + + EmitResult result = compilation.Emit( + peStream: assemblyStream, + pdbStream: symbolsStream, + embeddedTexts: embeddedTexts, + options: emitOptions); + + if (!result.Success) + { + var errors = new List(); + + IEnumerable failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + foreach (Diagnostic diagnostic in failures) + errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}"); + + throw new InvalidOperationException(string.Join("\n", errors)); + } + + assemblyStream.Seek(0, SeekOrigin.Begin); + symbolsStream.Seek(0, SeekOrigin.Begin); + +#if NETSTANDARD2_0 || NET6_0_OR_GREATER + return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream); +#else + return Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray()); +#endif + } + + } +} diff --git a/src/ExpressionDebugger/ExpressionDebugger.csproj b/src/ExpressionDebugger/ExpressionDebugger.csproj new file mode 100644 index 00000000..df9357e4 --- /dev/null +++ b/src/ExpressionDebugger/ExpressionDebugger.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + True + Chaowlert Chaisrichalermpol + Step into debugging from linq expressions + https://github.com/chaowlert/ExpressionDebugger + https://github.com/chaowlert/ExpressionDebugger + expression;linq;debug + https://cloud.githubusercontent.com/assets/5763993/26522656/41e28a6e-432f-11e7-9cae-7856f927d1a1.png + True + true + ExpressionDebugger.snk + 2.2.0 + https://github.com/chaowlert/ExpressionDebugger/blob/master/LICENSE + 8.0 + enable + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ExpressionDebugger/ExpressionDebugger.snk b/src/ExpressionDebugger/ExpressionDebugger.snk new file mode 100644 index 00000000..b7f3c897 Binary files /dev/null and b/src/ExpressionDebugger/ExpressionDebugger.snk differ diff --git a/src/ExpressionDebugger/ExpressionDebuggerExtensions.cs b/src/ExpressionDebugger/ExpressionDebuggerExtensions.cs new file mode 100644 index 00000000..29d2bae0 --- /dev/null +++ b/src/ExpressionDebugger/ExpressionDebuggerExtensions.cs @@ -0,0 +1,62 @@ +using ExpressionDebugger; +using System.Diagnostics; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace System.Linq.Expressions +{ + public static class ExpressionDebuggerExtensions + { + + /// + /// Compile with debugging info injected + /// + /// Type of lambda expression + /// Lambda expression + /// Compilation options + /// Generated method + public static T CompileWithDebugInfo(this Expression node, ExpressionCompilationOptions? options = null) + { + return (T)(object)CompileWithDebugInfo((LambdaExpression)node, options); + } + + public static Delegate CompileWithDebugInfo(this LambdaExpression node, ExpressionCompilationOptions? options = null) + { + try + { + var compiler = new ExpressionCompiler(options); + compiler.AddFile(node); + var assembly = compiler.CreateAssembly(); + + var translator = compiler.Translators[0]; + return translator.CreateDelegate(assembly); + } + catch (Exception ex) + { + if (options?.ThrowOnFailedCompilation == true) + throw; + Debug.Print(ex.ToString()); + return node.Compile(); + } + } + + public static Delegate CreateDelegate(this ExpressionTranslator translator, Assembly assembly) + { + var definitions = translator.Definitions!; + var typeName = definitions.Namespace == null + ? definitions.TypeName + : definitions.Namespace + "." + definitions.TypeName; + var type = assembly.GetType(typeName); + var main = translator.Methods.First(); + var method = type.GetMethod(main.Key)!; + var obj = definitions.IsStatic ? null : Activator.CreateInstance(type); + var flag = definitions.IsStatic ? BindingFlags.Static : BindingFlags.Instance; + foreach (var kvp in translator.Constants) + { + var field = type.GetField(kvp.Value, BindingFlags.NonPublic | flag)!; + field.SetValue(obj, kvp.Key); + } + return method.CreateDelegate(main.Value, obj); + } + } +} diff --git a/src/ExpressionTranslator/ExpressionDefinitions.cs b/src/ExpressionTranslator/ExpressionDefinitions.cs new file mode 100644 index 00000000..165d679d --- /dev/null +++ b/src/ExpressionTranslator/ExpressionDefinitions.cs @@ -0,0 +1,8 @@ +namespace ExpressionDebugger +{ + public class ExpressionDefinitions : TypeDefinitions + { + public string? MethodName { get; set; } + public bool IsExpression { get; set; } + } +} diff --git a/src/ExpressionTranslator/ExpressionTranslator.cs b/src/ExpressionTranslator/ExpressionTranslator.cs new file mode 100644 index 00000000..5e018e14 --- /dev/null +++ b/src/ExpressionTranslator/ExpressionTranslator.cs @@ -0,0 +1,2029 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +#if !NETSTANDARD1_3 +using System.Dynamic; +#endif +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace ExpressionDebugger +{ + public class ExpressionTranslator : ExpressionVisitor + { + private const int Tabsize = 4; + private StringWriter _writer; + private int _indentLevel; + private List? _appendWriters; + + private HashSet? _usings; + private Dictionary? _defaults; + + private Dictionary? _constants; + public Dictionary Constants => _constants ??= new Dictionary(); + + private Dictionary? _typeNames; + public Dictionary TypeNames => _typeNames ??= new Dictionary(); + + private Dictionary? _methods; + public Dictionary Methods => _methods ??= new Dictionary(); + + private List? _properties; + public List Properties => _properties ??= new List(); + + public bool HasDynamic { get; private set; } + public TypeDefinitions? Definitions { get; } + + public ExpressionTranslator(TypeDefinitions? definitions = null) + { + Definitions = definitions; + _writer = new StringWriter(); + ResetIndentLevel(); + } + + private void ResetIndentLevel() + { + _indentLevel = 0; + if (Definitions?.TypeName != null) + { + _indentLevel++; + if (Definitions.Namespace != null) + _indentLevel++; + } + } + + public static ExpressionTranslator Create(Expression node, ExpressionDefinitions? definitions = null) + { + var translator = new ExpressionTranslator(definitions); + if (node.NodeType == ExpressionType.Lambda) + translator.VisitLambda((LambdaExpression)node, + definitions?.IsExpression == true ? LambdaType.PublicLambda : LambdaType.PublicMethod, + definitions?.MethodName, + definitions?.IsInternal ?? false); + else + translator.Visit(node); + return translator; + } + + private static int GetPrecedence(ExpressionType nodeType) + { + switch (nodeType) + { + // Assignment + case ExpressionType.AddAssign: + case ExpressionType.AddAssignChecked: + case ExpressionType.AndAssign: + case ExpressionType.Assign: + case ExpressionType.DivideAssign: + case ExpressionType.ExclusiveOrAssign: + case ExpressionType.LeftShiftAssign: + case ExpressionType.ModuloAssign: + case ExpressionType.MultiplyAssign: + case ExpressionType.MultiplyAssignChecked: + case ExpressionType.OrAssign: + //case ExpressionType.PowerAssign: + case ExpressionType.Quote: + case ExpressionType.RightShiftAssign: + case ExpressionType.SubtractAssign: + case ExpressionType.SubtractAssignChecked: + case ExpressionType.Extension: + return 1; + + // Conditional + case ExpressionType.Coalesce: + case ExpressionType.Conditional: + return 2; + + // Conditional OR + case ExpressionType.OrElse: + return 3; + + // Conditional AND + case ExpressionType.AndAlso: + return 4; + + // Logical OR + case ExpressionType.Or: + return 5; + + // Logical XOR + case ExpressionType.ExclusiveOr: + return 6; + + // Logical AND + case ExpressionType.And: + return 7; + + // Equality + case ExpressionType.Equal: + case ExpressionType.NotEqual: + return 8; + + // Relational and type testing + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.TypeAs: + case ExpressionType.TypeEqual: + case ExpressionType.TypeIs: + return 9; + + // Shift + case ExpressionType.LeftShift: + case ExpressionType.RightShift: + return 10; + + // Additive + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Decrement: + case ExpressionType.Increment: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + return 11; + + // Multiplicative + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + return 12; + + // Unary + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.IsFalse: + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.OnesComplement: + case ExpressionType.PreDecrementAssign: + case ExpressionType.PreIncrementAssign: + case ExpressionType.UnaryPlus: + return 13; + + //// Power + //case ExpressionType.Power: + // return 14; + + default: + return 100; + } + } + + private static bool ShouldGroup(Expression? node, ExpressionType parentNodeType, bool isRightNode) + { + if (node == null) + return false; + + var nodePrecedence = GetPrecedence(node.NodeType); + var parentPrecedence = GetPrecedence(parentNodeType); + + if (nodePrecedence != parentPrecedence) + return nodePrecedence < parentPrecedence; + + switch (parentNodeType) + { + //wrap to prevent confusion + case ExpressionType.Conditional: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.UnaryPlus: + return true; + + //1-(1-1) != 1-1-1 + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.LeftShift: + //case ExpressionType.Power: + case ExpressionType.RightShift: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + return isRightNode; + + default: + return false; + } + } + + private Expression Visit(string open, Expression node, params string[] end) + { + Write(open); + var result = Visit(node)!; + Write(end); + return result; + } + + private void Write(string text) + { + _writer.Write(text); + } + + private void Write(params string[] texts) + { + foreach (var text in texts) + { + Write(text); + } + } + + private void WriteLine() + { + _writer.WriteLine(); + + var spaceCount = _indentLevel * Tabsize; + _writer.Write(new string(' ', spaceCount)); + } + + private Expression VisitNextLine(string open, Expression node, params string[] end) + { + WriteLine(); + Write(open); + var result = Visit(node)!; + Write(end); + return result; + } + + private void WriteNextLine(string text) + { + WriteLine(); + Write(text); + } + + private void WriteNextLine(params string[] texts) + { + WriteLine(); + foreach (var text in texts) + { + Write(text); + } + } + + private void Indent(bool inline = false) + { + if (!inline) + WriteLine(); + Write("{"); + _indentLevel++; + } + + private void Outdent() + { + _indentLevel--; + WriteNextLine("}"); + } + + private Expression VisitGroup(Expression node, ExpressionType parentNodeType, bool isRightNode = false) + { + Expression result; + if (!IsInline(node)) + { + var func = typeof(Func<>).MakeGenericType(node.Type); + Write("(new ", Translate(func), "(() => "); + Indent(true); + result = VisitMultiline(node, true); + Outdent(); + Write("))()"); + } + else if (ShouldGroup(node, parentNodeType, isRightNode)) + { + result = Visit("(", node, ")"); + } + else + { + result = Visit(node)!; + } + + return result; + } + + private static string Translate(ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.Add: return "+"; + case ExpressionType.AddChecked: return "+"; + case ExpressionType.AddAssign: return "+="; + case ExpressionType.AddAssignChecked: return "+="; + case ExpressionType.And: return "&"; + case ExpressionType.AndAlso: return "&&"; + case ExpressionType.AndAssign: return "&="; + case ExpressionType.ArrayLength: return ".Length"; + case ExpressionType.Assign: return "="; + case ExpressionType.Coalesce: return "??"; + case ExpressionType.Decrement: return " - 1"; + case ExpressionType.Divide: return "/"; + case ExpressionType.DivideAssign: return "/="; + case ExpressionType.Equal: return "=="; + case ExpressionType.ExclusiveOr: return "^"; + case ExpressionType.ExclusiveOrAssign: return "^="; + case ExpressionType.GreaterThan: return ">"; + case ExpressionType.GreaterThanOrEqual: return ">="; + case ExpressionType.Increment: return " + 1"; + case ExpressionType.IsFalse: return "!"; + case ExpressionType.IsTrue: return ""; + case ExpressionType.Modulo: return "%"; + case ExpressionType.ModuloAssign: return "%="; + case ExpressionType.Multiply: return "*"; + case ExpressionType.MultiplyAssign: return "*="; + case ExpressionType.MultiplyAssignChecked: return "*="; + case ExpressionType.MultiplyChecked: return "*"; + case ExpressionType.Negate: return "-"; + case ExpressionType.NegateChecked: return "-"; + case ExpressionType.Not: return "!"; + case ExpressionType.LeftShift: return "<<"; + case ExpressionType.LeftShiftAssign: return "<<="; + case ExpressionType.LessThan: return "<"; + case ExpressionType.LessThanOrEqual: return "<="; + case ExpressionType.NotEqual: return "!="; + case ExpressionType.OnesComplement: return "~"; + case ExpressionType.Or: return "|"; + case ExpressionType.OrAssign: return "|="; + case ExpressionType.OrElse: return "||"; + case ExpressionType.PreDecrementAssign: return "--"; + case ExpressionType.PreIncrementAssign: return "++"; + case ExpressionType.PostDecrementAssign: return "--"; + case ExpressionType.PostIncrementAssign: return "++"; + //case ExpressionType.Power: return "**"; + //case ExpressionType.PowerAssign: return "**="; + case ExpressionType.RightShift: return ">>"; + case ExpressionType.RightShiftAssign: return ">>="; + case ExpressionType.Subtract: return "-"; + case ExpressionType.SubtractChecked: return "-"; + case ExpressionType.SubtractAssign: return "-="; + case ExpressionType.SubtractAssignChecked: return "-="; + case ExpressionType.Throw: return "throw"; + case ExpressionType.TypeAs: return " as "; + case ExpressionType.UnaryPlus: return "+"; + case ExpressionType.Unbox: return ""; + + default: + throw new InvalidOperationException(); + } + } + + protected override Expression VisitBinary(BinaryExpression node) + { + Expression left, right; + if (node.NodeType == ExpressionType.ArrayIndex) + { + left = VisitGroup(node.Left, node.NodeType); + right = Visit("[", node.Right, "]"); + } + else if (node.NodeType == ExpressionType.Power || node.NodeType == ExpressionType.PowerAssign) + { + if (node.NodeType == ExpressionType.PowerAssign) + { + VisitGroup(node.Left, node.NodeType); + Write(" = "); + } + Write(Translate(typeof(Math)), ".Pow("); + left = Visit(node.Left)!; + Write(", "); + right = Visit(node.Right)!; + Write(")"); + } + else + { + left = VisitGroup(node.Left, node.NodeType); + Write(" ", Translate(node.NodeType), " "); + right = VisitGroup(node.Right, node.NodeType, true); + } + + return node.Update(left, node.Conversion, right); + } + + private byte? _nilCtx; + private byte[]? _nil; + private int _nilIndex; + private string TranslateNullable(Type type, byte? nullableContext, byte[]? nullable) + { + try + { + _nilCtx = nullableContext; + _nil = nullable; + _nilIndex = 0; + return Translate(type); + } + finally + { + _nilCtx = null; + _nil = null; + } + } + public string Translate(Type type) + { + var refNullable = !type.GetTypeInfo().IsValueType && + (_nilIndex < _nil?.Length ? _nil[_nilIndex++] == 2 : _nilCtx == 2); + var typeName = TranslateInner(type); + return refNullable ? $"{typeName}?" : typeName; + } + private string TranslateInner(Type type) + { + if (type == typeof(bool)) + return "bool"; + if (type == typeof(byte)) + return "byte"; + if (type == typeof(char)) + return "char"; + if (type == typeof(decimal)) + return "decimal"; + if (type == typeof(double)) + return "double"; + if (type == typeof(float)) + return "float"; + if (type == typeof(int)) + return "int"; + if (type == typeof(long)) + return "long"; + if (type == typeof(object)) + return "object"; + if (type == typeof(sbyte)) + return "sbyte"; + if (type == typeof(short)) + return "short"; + if (type == typeof(string)) + return "string"; + if (type == typeof(uint)) + return "uint"; + if (type == typeof(ulong)) + return "ulong"; + if (type == typeof(ushort)) + return "ushort"; + if (type == typeof(void)) + return "void"; +#if !NETSTANDARD1_3 + if (typeof(IDynamicMetaObjectProvider).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + HasDynamic = true; + return "dynamic"; + } +#endif + + if (type.IsArray) + { + var rank = type.GetArrayRank(); + return Translate(type.GetElementType()!) + "[" + new string(',', rank - 1) + "]"; + } + + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType != null) + return Translate(underlyingType) + "?"; + + _usings ??= new HashSet(); + + string name; + if (_nilCtx != null || _nil != null) + { + name = GetTypeName(type); + if (Definitions?.PrintFullTypeName != true && !string.IsNullOrEmpty(type.Namespace)) + _usings.Add(type.Namespace); + } + else if (!this.TypeNames.TryGetValue(type, out name)) + { + name = GetTypeName(type); + + if (Definitions?.PrintFullTypeName != true) + { + var count = this.TypeNames.Count(kvp => GetTypeName(kvp.Key) == name); + if (count > 0) + { + // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName + // keep logic here for compatability + if (!type.GetTypeInfo().IsGenericType) + name += count + 1; + else if (!string.IsNullOrEmpty(type.Namespace)) + name = type.Namespace + '.' + name; + } + else if (!string.IsNullOrEmpty(type.Namespace)) + _usings.Add(type.Namespace); + } + this.TypeNames.Add(type, name); + } + + return name; + } + + private static string GetVarName(string name) + { + var index = name.IndexOf('`'); + if (index >= 0) + name = name.Substring(0, index); + return name; + } + + private string GetTypeName(Type type) + { + var name = GetSingleTypeName(type); + if (type.DeclaringType == null) + return name; + + return TranslateInner(type.DeclaringType) + "." + name; + } + + private string GetSingleTypeName(Type type) + { + var name = type.DeclaringType == null && Definitions?.PrintFullTypeName == true + ? type.FullName! + : type.Name; + if (!type.GetTypeInfo().IsGenericType) + { + return name; + } + + var index = name.IndexOf('`'); + if (index >= 0) + name = name.Substring(0, index); + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + var typeArgs = type.GetGenericArguments(); + return name + "<" + new string(',', typeArgs.Length - 1) + ">"; + } + + return name + "<" + string.Join(", ", type.GetGenericArguments().Select(Translate)) + ">"; + } + + private static bool IsInline(Expression node) + { + switch (node.NodeType) + { + case ExpressionType.Conditional: + var condExpr = (ConditionalExpression)node; + return condExpr.Type != typeof(void) && IsInline(condExpr.IfTrue) && IsInline(condExpr.IfFalse); + + case ExpressionType.Block: + case ExpressionType.DebugInfo: + //case ExpressionType.Goto: + case ExpressionType.Label: + case ExpressionType.Loop: + case ExpressionType.Switch: + //case ExpressionType.Throw: + case ExpressionType.Try: + return false; + default: + return true; + } + } + + private Expression VisitMultiline(Expression node, bool shouldReturn) + { + switch (node.NodeType) + { + case ExpressionType.Block: + return VisitBlock((BlockExpression)node, shouldReturn); + + case ExpressionType.Conditional: + return VisitConditional((ConditionalExpression)node, shouldReturn); + + case ExpressionType.Try: + return VisitTry((TryExpression)node, shouldReturn); + + case ExpressionType.Switch: + return VisitSwitch((SwitchExpression)node, shouldReturn); + + //case ExpressionType.DebugInfo: + //case ExpressionType.Goto: + //case ExpressionType.Loop: + default: + return Visit(node)!; + } + } + + private Expression VisitBody(Expression node, bool shouldReturn = false) + { + if (node.NodeType == ExpressionType.Block) + return VisitBlock((BlockExpression)node, shouldReturn); + + if (node.NodeType == ExpressionType.Default && node.Type == typeof(void)) + return node; + + var lines = VisitBlockBody(new List { node }, shouldReturn); + return Expression.Block(lines); + } + + private IEnumerable VisitBlockBody(IList exprs, bool shouldReturn) + { + var lines = new List(); + var last = exprs.Count - 1; + for (int i = 0; i < exprs.Count; i++) + { + var expr = exprs[i]; + if (expr.NodeType == ExpressionType.Default && expr.Type == typeof(void)) + continue; + + var isInline = IsInline(expr); + if (isInline || i > 0) + WriteLine(); + + Expression next; + if (isInline) + { + if (shouldReturn && i == last && expr.NodeType != ExpressionType.Throw) + Write("return "); + next = Visit(expr)!; + Write(";"); + } + else + { + next = VisitMultiline(expr, shouldReturn && i == last); + } + lines.Add(next); + } + return lines; + } + + private Expression VisitBlock(BlockExpression node, bool shouldReturn) + { + var assignedVariables = node.Expressions + .Where(exp => exp.NodeType == ExpressionType.Assign) + .Select(exp => ((BinaryExpression)exp).Left) + .Where(exp => exp.NodeType == ExpressionType.Parameter) + .Cast() + .ToHashSet(); + + var list = new List(); + var hasDeclaration = false; + foreach (var variable in node.Variables) + { + Expression arg; + if (assignedVariables.Contains(variable)) + { + arg = VisitParameter(variable, false); + } + else + { + arg = VisitNextLine(Translate(variable.Type) + " ", variable, ";"); + hasDeclaration = true; + } + list.Add((ParameterExpression)arg); + } + if (hasDeclaration) + WriteLine(); + + var lines = VisitBlockBody(node.Expressions, shouldReturn && node.Type != typeof(void)); + return Expression.Block(list, lines); + } + + protected override Expression VisitBlock(BlockExpression node) + { + return VisitBlock(node, false); + } + + private CatchBlock VisitCatchBlock(CatchBlock node, bool shouldReturn) + { + WriteNextLine("catch (", Translate(node.Test)); + if (node.Variable != null) + { + Visit(" ", node.Variable); + } + Write(")"); + + var filter = node.Filter; + if (filter != null) + { + filter = Visit(" when (", filter, ")"); + } + Indent(); + var body = VisitBody(node.Body, shouldReturn); + Outdent(); + return node.Variable != null + ? Expression.Catch(node.Variable, body, filter) + : Expression.Catch(node.Test, body, filter); + } + + protected override CatchBlock VisitCatchBlock(CatchBlock node) + { + return VisitCatchBlock(node, false); + } + + private Expression VisitConditional(ConditionalExpression node, bool shouldReturn) + { + if (IsInline(node)) + { + Expression test = VisitGroup(node.Test, node.NodeType); + Write(" ? "); + Expression ifTrue = VisitGroup(node.IfTrue, node.NodeType); + Write(" : "); + Expression ifFalse = VisitGroup(node.IfFalse, node.NodeType); + return node.Update(test, ifTrue, ifFalse); + } + else + { + return VisitConditionalBlock(node, shouldReturn); + } + } + + private Expression VisitConditionalBlock(ConditionalExpression node, bool shouldReturn, bool chain = false) + { + WriteNextLine(chain ? "else if (" : "if ("); + var test = Visit(node.Test)!; + Write(")"); + Indent(); + Expression ifTrue = VisitBody(node.IfTrue, shouldReturn); + Expression ifFalse = node.IfFalse; + if (node.IfFalse.NodeType != ExpressionType.Default) + { + Outdent(); + if (node.IfFalse.NodeType == ExpressionType.Conditional) + { + ifFalse = VisitConditionalBlock((ConditionalExpression)node.IfFalse, shouldReturn, true); + } + else + { + WriteNextLine("else"); + Indent(); + ifFalse = VisitBody(node.IfFalse, shouldReturn); + Outdent(); + } + } + else + { + Outdent(); + } + + Expression condition = Expression.Condition(test, ifTrue, ifFalse, typeof(void)); + return CreateBlock(condition); + } + + protected override Expression VisitConditional(ConditionalExpression node) + { + return VisitConditional(node, false); + } + + private void WriteValue(object? value) + { + if (value == null) + { + Write("null"); + } + else if (value is string str) + { + if (str.IndexOf('\\') >= 0 || str.IndexOf('\n') >= 0 || str.IndexOf('"') >= 0) + { + str = str.Replace(@"""", @""""""); + Write($"@\"{str}\""); + } + else + { + Write($"\"{str}\""); + } + } + else if (value is char c) + { + if (c == '\\') + Write(@"'\\'"); + else if (c == '\'') + Write(@"'\''"); + else + Write($"'{c}'"); + } + else if (value is bool) + { + Write(value.ToString().ToLower()); + } + else if (value is Type t) + { + Write($"typeof({Translate(t)})"); + } + else if (value is int) + { + Write(value.ToString()); + } + else if (value is double d) + { + if (double.IsNaN(d)) + Write("double.NaN"); + else if (double.IsPositiveInfinity(d)) + Write("double.PositiveInfinity"); + else if (double.IsNegativeInfinity(d)) + Write("double.NegativeInfinity"); + else + Write(d.ToString(CultureInfo.InvariantCulture), "d"); + } + else if (value is float f) + { + if (float.IsNaN(f)) + Write("float.NaN"); + else if (float.IsPositiveInfinity(f)) + Write("float.PositiveInfinity"); + else if (float.IsNegativeInfinity(f)) + Write("float.NegativeInfinity"); + else + Write(f.ToString(CultureInfo.InvariantCulture), "f"); + } + else if (value is decimal || value is long || value is uint || value is ulong) + { + Write(value.ToString(), GetLiteral(value.GetType())); + } + else if (value is byte || value is sbyte || value is short || value is ushort) + { + Write("((", Translate(value.GetType()), ")", value.ToString(), ")"); + } + else if (value.GetType().GetTypeInfo().IsEnum) + { + var name = Enum.GetName(value.GetType(), value); + if (name != null) + Write(Translate(value.GetType()), ".", name); + else + Write("(", Translate(value.GetType()), ")", value.ToString()); + } + else + { + var type = value.GetType(); + if (type.GetTypeInfo().IsValueType) + { + _defaults ??= new Dictionary(); + if (!_defaults.TryGetValue(type, out var def)) + { + def = Activator.CreateInstance(type); + _defaults[type] = def; + } + if (value.Equals(def)) + { + Write($"default({Translate(type)})"); + return; + } + } + Write(GetConstant(value)); + } + } + + protected override Expression VisitConstant(ConstantExpression node) + { + WriteValue(node.Value); + return node; + } + + private static string GetLiteral(Type type) + { + if (type == typeof(decimal)) + return "m"; + else if (type == typeof(long)) + return "l"; + else if (type == typeof(uint)) + return "u"; + else if (type == typeof(ulong)) + return "ul"; + else if (type == typeof(double)) + return "d"; + else if (type == typeof(float)) + return "f"; + else + return ""; + } + + protected override Expression VisitDefault(DefaultExpression node) + { + Write("default(", Translate(node.Type), ")"); + return node; + } + +#if !NETSTANDARD1_3 + private static Expression Update(DynamicExpression node, IEnumerable args) + { + // ReSharper disable PossibleMultipleEnumeration + return node.Arguments.SequenceEqual(args) ? node : node.Update(args); + // ReSharper restore PossibleMultipleEnumeration + } + + protected override Expression VisitDynamic(DynamicExpression node) + { + if (node.Binder is ConvertBinder convert) + { + Write("(", Translate(convert.Type), ")"); + var expr = VisitGroup(node.Arguments[0], ExpressionType.Convert); + return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); + } + if (node.Binder is GetMemberBinder getMember) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); + Write(".", getMember.Name); + return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); + } + if (node.Binder is SetMemberBinder setMember) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); + Write(".", setMember.Name, " = "); + var value = VisitGroup(node.Arguments[1], ExpressionType.Assign); + return Update(node, new[] { expr, value }.Concat(node.Arguments.Skip(2))); + } + if (node.Binder is DeleteMemberBinder deleteMember) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); + Write(".", deleteMember.Name, " = null"); + return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); + } + if (node.Binder is GetIndexBinder) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); + var args = VisitArguments("[", node.Arguments.Skip(1).ToList(), Visit, "]"); + return Update(node, new[] { expr }.Concat(args)); + } + if (node.Binder is SetIndexBinder) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); + var args = VisitArguments("[", node.Arguments.Skip(1).Take(node.Arguments.Count - 2).ToList(), Visit, "]"); + Write(" = "); + var value = VisitGroup(node.Arguments[node.Arguments.Count - 1], ExpressionType.Assign); + return Update(node, new[] { expr }.Concat(args).Concat(new[] { value })); + } + if (node.Binder is DeleteIndexBinder) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.Index); + var args = VisitArguments("[", node.Arguments.Skip(1).ToList(), Visit, "]"); + Write(" = null"); + return Update(node, new[] { expr }.Concat(args)); + } + if (node.Binder is InvokeMemberBinder invokeMember) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.MemberAccess); + Write(".", invokeMember.Name); + var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); + return Update(node, new[] { expr }.Concat(args)); + } + if (node.Binder is InvokeBinder) + { + var expr = VisitGroup(node.Arguments[0], ExpressionType.Invoke); + var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); + return Update(node, new[] { expr }.Concat(args)); + } + if (node.Binder is CreateInstanceBinder) + { + Write("new "); + var expr = VisitGroup(node.Arguments[0], ExpressionType.Invoke); + var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")"); + return Update(node, new[] { expr }.Concat(args)); + } + if (node.Binder is UnaryOperationBinder unary) + { + var expr = VisitUnary(node.Arguments[0], unary.Operation); + return Update(node, new[] { expr }.Concat(node.Arguments.Skip(1))); + } + if (node.Binder is BinaryOperationBinder binary) + { + var left = VisitGroup(node.Arguments[0], node.NodeType); + Write(" ", Translate(binary.Operation), " "); + var right = VisitGroup(node.Arguments[1], node.NodeType, true); + return Update(node, new[] { left, right }.Concat(node.Arguments.Skip(2))); + } + Write("dynamic"); + var dynArgs = VisitArguments("(" + Translate(node.Binder.GetType()) + ", ", node.Arguments, Visit, ")"); + return node.Update(dynArgs); + } +#endif + + private IList VisitArguments(string open, IList args, Func func, string end, bool wrap = false, IList? prefix = null) where T : class + { + Write(open); + if (wrap) + _indentLevel++; + + var list = new List(); + var last = args.Count - 1; + var changed = false; + for (var i = 0; i < args.Count; i++) + { + if (wrap) + WriteLine(); + if (prefix != null) + Write(prefix[i]); + var arg = func(args[i]); + changed |= arg != args[i]; + list.Add(arg); + if (i != last) + Write(wrap ? "," : ", "); + } + if (wrap) + { + _indentLevel--; + WriteLine(); + } + Write(end); + return changed ? list : args; + } + + protected override ElementInit VisitElementInit(ElementInit node) + { + if (node.Arguments.Count == 1) + { + var arg = Visit(node.Arguments[0]); + var args = arg != node.Arguments[0] ? new[] { arg }.AsEnumerable() : node.Arguments; + return node.Update(args); + } + else + { + var list = VisitArguments("{", node.Arguments, Visit, "}"); + return node.Update(list); + } + } + + private Dictionary? _counter; + private Dictionary? _ids; + + private string GetConstant(object obj, string? typeName = null) + { + if (this.Constants.TryGetValue(obj, out var name)) + return name; + + _counter ??= new Dictionary(); + + typeName ??= GetVarName(obj.GetType().Name); + + _counter.TryGetValue(typeName, out var id); + id++; + _counter[typeName] = id; + name = typeName + id; + this.Constants[obj] = name; + return name; + } + + private string GetName(string type, object obj) + { + _ids ??= new Dictionary(); + + if (_ids.TryGetValue(obj, out int id)) + return type + id; + + _counter ??= new Dictionary(); + _counter.TryGetValue(type, out id); + id++; + _counter[type] = id; + _ids[obj] = id; + return type + id; + } + + private string GetName(LabelTarget label) + { + return string.IsNullOrEmpty(label.Name) ? GetName("label", label) : label.Name; + } + + private string GetName(ParameterExpression param) + { + if (string.IsNullOrEmpty(param.Name)) + return GetName("p", param); + else if (ReservedWords.Contains(param.Name)) + return "@" + param.Name; + else + return param.Name; + } + + private string GetName(LambdaExpression lambda, string defaultMethodName = "Main") + { + var main = Definitions?.TypeName != null + ? defaultMethodName + : ""; + return string.IsNullOrEmpty(lambda.Name) ? GetName("func" + main, lambda) : lambda.Name; + } + + private HashSet? _returnTargets; + protected override Expression VisitGoto(GotoExpression node) + { + switch (node.Kind) + { + case GotoExpressionKind.Goto: + Write("goto ", GetName(node.Target)); + break; + case GotoExpressionKind.Return: + _returnTargets ??= new HashSet(); + _returnTargets.Add(node.Target); + var value = Visit("return ", node.Value); + return node.Update(node.Target, value); + case GotoExpressionKind.Break: + Write("break"); + break; + case GotoExpressionKind.Continue: + Write("continue"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(node)); + } + return node; + } + + private Expression? VisitMember(Expression? instance, Expression node, MemberInfo member) + { + if (instance != null) + { + var result = VisitGroup(instance, node.NodeType); + Write(".", member.Name); + return result; + } + else + { + Write(Translate(member.DeclaringType!), ".", member.Name); + return null; + } + } + + protected override Expression VisitIndex(IndexExpression node) + { + var obj = node.Indexer != null && node.Indexer.DeclaringType!.GetCustomAttribute()?.MemberName != node.Indexer.Name + ? VisitMember(node.Object, node, node.Indexer) + : VisitGroup(node.Object, node.NodeType); + + var args = VisitArguments("[", node.Arguments, Visit, "]"); + + return node.Update(obj!, args); + } + + protected override Expression VisitInvocation(InvocationExpression node) + { + var exp = VisitGroup(node.Expression, node.NodeType); + var args = VisitArguments("(", node.Arguments, Visit, ")"); + return node.Update(exp, args); + } + + protected override Expression VisitLabel(LabelExpression node) + { + if (_returnTargets == null || !_returnTargets.Contains(node.Target)) + Write(GetName(node.Target), ":"); + return node; + } + + private ParameterExpression VisitParameterDeclaration(ParameterExpression node) + { + if (node.Type.IsByRef) + Write("ref "); + return (ParameterExpression)Visit(Translate(node.Type) + " ", node); + } + + private void WriteModifier(string modifier) + { + Write(modifier, " "); + if (Definitions?.IsStatic == true) + Write("static "); + } + private void WriteModifierNextLine(string modifier) + { + WriteLine(); + WriteModifier(modifier); + } + + private int _inlineCount; + public Expression VisitLambda(LambdaExpression node, LambdaType type, string? methodName = null, bool isInternal = false) + { + if (type == LambdaType.PrivateLambda || type == LambdaType.PublicLambda) + { + _inlineCount++; + if (type == LambdaType.PublicLambda) + { + var name = methodName ?? "Main"; + if (!isInternal) + isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); + WriteModifierNextLine(isInternal ? "internal" : "public"); + var funcType = MakeDelegateType(node.ReturnType, node.Parameters.Select(it => it.Type).ToArray()); + var exprType = typeof(Expression<>).MakeGenericType(funcType); + Write(Translate(exprType), " ", name, " => "); + } + IList args; + if (node.Parameters.Count == 1) + { + args = new List(); + 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 ?? "Main"; + if (type == LambdaType.PublicMethod || type == LambdaType.ExtensionMethod) + { + if (!isInternal) + isInternal = node.ReturnType.GetTypeInfo().IsNotPublic || node.Parameters.Any(it => it.Type.GetTypeInfo().IsNotPublic); + WriteModifierNextLine(isInternal ? "internal" : "public"); + this.Methods[name] = node.Type; + } + else + { + name = GetName(node, name); + WriteModifierNextLine("private"); + } + Write(Translate(node.ReturnType), " ", name); + var open = "("; + if (type == LambdaType.ExtensionMethod) + { + if (this.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? _visitedLambda; + private int _writerLevel; + protected override Expression VisitLambda(Expression node) + { + if (_inlineCount > 0) + return VisitLambda(node, LambdaType.PrivateLambda); + + Write(GetName(node)); + + _visitedLambda ??= new HashSet(); + if (_visitedLambda.Contains(node)) + return node; + _visitedLambda.Add(node); + + //switch writer to append writer + _appendWriters ??= new List(); + if (_writerLevel == _appendWriters.Count) + _appendWriters.Add(new StringWriter()); + + var temp = _writer; + var oldIndent = _indentLevel; + try + { + _writer = _appendWriters[_writerLevel]; + _writerLevel++; + ResetIndentLevel(); + + WriteLine(); + return VisitLambda(node, LambdaType.PrivateMethod); + } + finally + { + //switch back + _writer = temp; + _indentLevel = oldIndent; + _writerLevel--; + } + } + + private IList VisitElements(IList list, Func func) where T : class + { + var wrap = true; + if (list.Count == 0) + { + wrap = false; + } + else if (list.Count <= 4) + { + wrap = list[0] is MemberBinding && list.Count > 1; + } + if (wrap) + WriteLine(); + else + Write(" "); + return VisitArguments("{", list, func, "}", wrap); + } + + protected override Expression VisitListInit(ListInitExpression node) + { + var @new = (NewExpression)Visit(node.NewExpression)!; + var args = VisitElements(node.Initializers, VisitElementInit); + return node.Update(@new, args); + } + + protected override Expression VisitLoop(LoopExpression node) + { + Expression body; + if (node.Body.NodeType == ExpressionType.Conditional) + { + var condExpr = (ConditionalExpression)node.Body; + + if (condExpr.IfFalse is GotoExpression @break && @break.Target == node.BreakLabel) + { + WriteNextLine("while ("); + var test = Visit(condExpr.Test)!; + Write(")"); + Indent(); + body = VisitBody(condExpr.IfTrue); + Outdent(); + var outBreak = CreateBlock(@break); + + Expression condition = Expression.Condition(test, body, outBreak, typeof(void)); + condition = CreateBlock(condition); + return Expression.Loop( + condition, + node.BreakLabel, + node.ContinueLabel); + } + } + + WriteNextLine("while (true)"); + Indent(); + body = VisitBody(node.Body); + Outdent(); + return Expression.Loop(body, node.BreakLabel, node.ContinueLabel); + } + + protected override Expression VisitMember(MemberExpression node) + { + var expr = VisitMember(node.Expression, node, node.Member)!; + return node.Update(expr); + } + + protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) + { + Write(node.Member.Name, " = "); + var expr = Visit(node.Expression)!; + return node.Update(expr); + } + + protected override Expression VisitMemberInit(MemberInitExpression node) + { + var @new = (NewExpression)Visit(node.NewExpression)!; + var args = VisitElements(node.Bindings, VisitMemberBinding); + return node.Update(@new, args); + } + + protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) + { + Write(node.Member.Name, " ="); + var args = VisitElements(node.Initializers, VisitElementInit); + return node.Update(args); + } + + protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) + { + Write(node.Member.Name, " ="); + var args = VisitElements(node.Bindings, VisitMemberBinding); + return node.Update(args); + } + + private static Type MakeDelegateType(Type returnType, params Type[] parameters) + { + var del = GetDelegateType(returnType != typeof(void), parameters.Length); + if (del.GetTypeInfo().IsGenericTypeDefinition) + { + var types = parameters.AsEnumerable(); + if (returnType != typeof(void)) + types = types.Concat(new[] { returnType }); + del = del.MakeGenericType(types.ToArray()); + } + + return del; + } + + private static Type GetDelegateType(bool isFunc, int argCount) + { + if (!isFunc) + { + switch (argCount) + { + case 0: return typeof(Action); + case 1: return typeof(Action<>); + case 2: return typeof(Action<,>); + case 3: return typeof(Action<,,>); + case 4: return typeof(Action<,,,>); + case 5: return typeof(Action<,,,,>); + case 6: return typeof(Action<,,,,,>); + case 7: return typeof(Action<,,,,,,>); + case 8: return typeof(Action<,,,,,,,>); + case 9: return typeof(Action<,,,,,,,,>); + case 10: return typeof(Action<,,,,,,,,,>); + case 11: return typeof(Action<,,,,,,,,,,>); + case 12: return typeof(Action<,,,,,,,,,,,>); + case 13: return typeof(Action<,,,,,,,,,,,,>); + case 14: return typeof(Action<,,,,,,,,,,,,,>); + case 15: return typeof(Action<,,,,,,,,,,,,,,>); + case 16: return typeof(Action<,,,,,,,,,,,,,,,>); + default: throw new InvalidOperationException("Cannot handle non-public method"); + } + } + else + { + switch (argCount) + { + case 0: return typeof(Func<>); + case 1: return typeof(Func<,>); + case 2: return typeof(Func<,,>); + case 3: return typeof(Func<,,,>); + case 4: return typeof(Func<,,,,>); + case 5: return typeof(Func<,,,,,>); + case 6: return typeof(Func<,,,,,,>); + case 7: return typeof(Func<,,,,,,,>); + case 8: return typeof(Func<,,,,,,,,>); + case 9: return typeof(Func<,,,,,,,,,>); + case 10: return typeof(Func<,,,,,,,,,,>); + case 11: return typeof(Func<,,,,,,,,,,,>); + case 12: return typeof(Func<,,,,,,,,,,,,>); + case 13: return typeof(Func<,,,,,,,,,,,,,>); + case 14: return typeof(Func<,,,,,,,,,,,,,,>); + case 15: return typeof(Func<,,,,,,,,,,,,,,,>); + case 16: return typeof(Func<,,,,,,,,,,,,,,,,>); + default: throw new InvalidOperationException("Cannot handle non-public method"); + } + } + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + var isExtension = false; + var isNotPublic = false; + Expression? arg0 = null; + + var obj = node.Object; + if (obj != null) + { + obj = VisitGroup(node.Object!, node.NodeType); + } +#if !NET40 + else if (!node.Method.IsPublic || node.Method.DeclaringType?.GetTypeInfo().IsNotPublic == true) + { + isNotPublic = true; + if (node.Method.GetParameters().Any(it => it.IsOut || it.ParameterType.IsByRef)) + throw new InvalidOperationException("Cannot handle non-public method"); + + var del = MakeDelegateType(node.Method.ReturnType, node.Method.GetParameters().Select(it => it.ParameterType).ToArray()); + var func = node.Method.CreateDelegate(del); + Write(GetConstant(func, GetVarName(node.Method.Name)), ".Invoke"); + } +#endif + else if (node.Method.GetCustomAttribute() != null) + { + isExtension = true; + arg0 = VisitGroup(node.Arguments[0], node.NodeType); + if (!string.IsNullOrEmpty(node.Method.DeclaringType?.Namespace)) + { + _usings ??= new HashSet(); + _usings.Add(node.Method.DeclaringType!.Namespace); + } + } + else if (node.Method.DeclaringType != null) + { + Write(Translate(node.Method.DeclaringType)); + } + + if (node.Method.IsSpecialName && node.Method.Name.StartsWith("get_")) + { + var attr = node.Method.DeclaringType!.GetCustomAttribute(); + if (attr?.MemberName == node.Method.Name.Substring(4)) + { + var keys = VisitArguments("[", node.Arguments, Visit, "]"); + return node.Update(obj, keys); + } + } + + if (!isNotPublic) + { + if (node.Method.DeclaringType != null) + Write("."); + Write(node.Method.Name); + if (node.Method.IsGenericMethod) + { + var args = string.Join(", ", node.Method.GetGenericArguments().Select(Translate)); + Write("<", args, ">"); + } + } + var prefix = node.Method.GetParameters() + .Select(p => p.IsOut ? "out " : p.ParameterType.IsByRef ? "ref " : ""); + + if (isExtension) + { + var args = VisitArguments("(", node.Arguments.Skip(1).ToList(), Visit, ")", prefix: prefix.Skip(1).ToList()); + var newArgs = new[] { arg0 }.Concat(args).ToList(); + return newArgs.SequenceEqual(node.Arguments) ? node : node.Update(obj, newArgs); + } + else + { + var args = VisitArguments("(", node.Arguments, Visit, ")", prefix: prefix.ToList()); + return node.Update(obj, args); + } + } + + protected override Expression VisitNew(NewExpression node) + { + Write("new ", Translate(node.Type)); + var args = VisitArguments("(", node.Arguments, Visit, ")"); + return node.Update(args); + } + + protected override Expression VisitNewArray(NewArrayExpression node) + { + if (node.NodeType == ExpressionType.NewArrayBounds) + { + var elemType = node.Type.GetElementType(); + var arrayCount = 1; + // ReSharper disable once PossibleNullReferenceException + while (elemType.IsArray) + { + elemType = elemType.GetElementType(); + arrayCount++; + } + Write("new ", Translate(elemType)); + var args = VisitArguments("[", node.Expressions, Visit, "]"); + for (int i = 1; i < arrayCount; i++) + Write("[]"); + return node.Update(args); + } + else + { + Write("new ", Translate(node.Type)); + var args = VisitElements(node.Expressions, Visit); + return node.Update(args); + } + } + + #region _reservedWords + private static readonly HashSet ReservedWords = new HashSet + { + "abstract", + "as", + "base", + "bool", + "break", + "by", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "descending", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "finally", + "fixed", + "float", + "for", + "foreach", + "from", + "goto", + "group", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "into", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "orderby", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "select", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "try", + "typeof", + "ulong", + "unchecked", + "unit", + "unsafe", + "ushort", + "using", + "var", + "virtual", + "void", + "volatile", + "where", + "while", + "yield", + "false", + "true", + }; + #endregion + + protected override Expression VisitParameter(ParameterExpression node) + { + return VisitParameter(node, true); + } + + private HashSet? _pendingVariables; + private Dictionary? _params; + private Expression VisitParameter(ParameterExpression node, bool write) + { + _pendingVariables ??= new HashSet(); + + var name = GetName(node); + if (write) + { + if (_pendingVariables.Contains(node)) + { + Write(Translate(node.Type), " ", name); + _pendingVariables.Remove(node); + } + else + { + Write(name); + } + } + else + { + _pendingVariables.Add(node); + } + + if (!string.IsNullOrEmpty(node.Name)) + return node; + + _params ??= new Dictionary(); + if (!_params.TryGetValue(node, out var result)) + { + result = Expression.Parameter(node.Type, name); + _params[node] = result; + } + return result; + } + + protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) + { + Write(GetConstant(node, "RuntimeVariables")); + return node; + } + + private Expression VisitSwitch(SwitchExpression node, bool shouldReturn) + { + WriteNextLine("switch ("); + var value = Visit(node.SwitchValue)!; + Write(")"); + Indent(); + + var cases = node.Cases.Select(c => VisitSwitchCase(c, shouldReturn)).ToList(); + var @default = node.DefaultBody; + if (@default != null) + { + WriteNextLine("default:"); + _indentLevel++; + @default = VisitBody(node.DefaultBody, shouldReturn); + if (!shouldReturn) + { + WriteLine(); + Write("break;"); + } + _indentLevel--; + Outdent(); + @default = CreateBlock(@default); + } + else + { + Outdent(); + } + + node = node.Update(value, cases, @default); + return CreateBlock(node); + } + + private static BlockExpression CreateBlock(params Expression[] exprs) + { + return Expression.Block(exprs.Where(expr => expr != null)); + } + + protected override Expression VisitSwitch(SwitchExpression node) + { + return VisitSwitch(node, false); + } + + private SwitchCase VisitSwitchCase(SwitchCase node, bool shouldReturn) + { + var values = node.TestValues.Select(test => VisitNextLine("case ", test, ":")).ToList(); + _indentLevel++; + var body = VisitBody(node.Body, shouldReturn); + if (!shouldReturn) + { + WriteLine(); + Write("break;"); + body = CreateBlock(body); + } + _indentLevel--; + return node.Update(values, body); + } + + protected override SwitchCase VisitSwitchCase(SwitchCase node) + { + return VisitSwitchCase(node, false); + } + + private Expression VisitTry(TryExpression node, bool shouldReturn) + { + string? faultParam = null; + if (node.Fault != null) + { + faultParam = GetName("fault", node); + WriteNextLine("bool ", faultParam, " = true;"); + } + WriteNextLine("try"); + Indent(); + var body = VisitBody(node.Body, shouldReturn); + if (node.Fault != null) + WriteNextLine(faultParam!, " = false;"); + Outdent(); + var handlers = node.Handlers.Select(c => VisitCatchBlock(c, shouldReturn)).ToList(); + var @finally = node.Finally; + var fault = node.Fault; + if (node.Finally != null || node.Fault != null) + { + WriteNextLine("finally"); + Indent(); + if (node.Finally != null) + @finally = VisitBody(node.Finally); + if (node.Fault != null) + { + WriteNextLine("if (", faultParam!, ")"); + Indent(); + fault = VisitBody(node.Fault); + Outdent(); + } + Outdent(); + } + return node.Update(body, handlers, @finally, fault); + } + + protected override Expression VisitTry(TryExpression node) + { + return VisitTry(node, false); + } + + protected override Expression VisitTypeBinary(TypeBinaryExpression node) + { + var expr = VisitGroup(node.Expression, node.NodeType); + Write(" is ", Translate(node.TypeOperand)); + return node.Update(expr); + } + + private Expression VisitUnary(Expression operand, ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.IsFalse: + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.PreDecrementAssign: + case ExpressionType.PreIncrementAssign: + case ExpressionType.OnesComplement: + case ExpressionType.UnaryPlus: + Write(Translate(nodeType)); + break; + } + + var result = VisitGroup(operand, nodeType); + + switch (nodeType) + { + case ExpressionType.ArrayLength: + case ExpressionType.Decrement: + case ExpressionType.Increment: + case ExpressionType.PostDecrementAssign: + case ExpressionType.PostIncrementAssign: + Write(Translate(nodeType)); + break; + } + return result; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + switch (node.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + //if (!node.Type.IsAssignableFrom(node.Operand.Type)) + Write("(", Translate(node.Type), ")"); + break; + + case ExpressionType.Throw: + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // ReSharper disable HeuristicUnreachableCode + if (node.Operand == null) + { + Write("throw"); + return node; + } + // ReSharper restore HeuristicUnreachableCode + Write("throw "); + break; + } + + var operand = node.NodeType == ExpressionType.Quote && node.Operand.NodeType == ExpressionType.Lambda + ? VisitLambda((LambdaExpression)node.Operand, LambdaType.PrivateLambda) + : VisitUnary(node.Operand, node.NodeType); + + switch (node.NodeType) + { + case ExpressionType.TypeAs: + Write(" as ", Translate(node.Type)); + break; + } + return node.Update(operand); + } + + public override string ToString() + { + var codeWriter = new StringWriter(); + var temp = _writer; + var oldIndent = _indentLevel; + _indentLevel = 0; + + try + { + _writer = codeWriter; + + //exercise to update _usings + var implements = Definitions?.Implements?.OrderBy(it => it.GetTypeInfo().IsInterface ? 1 : 0) + .Select(Translate) + .ToList(); + var constants = _constants?.OrderBy(it => it.Value) + .Select(kvp => $"{Translate(kvp.Key.GetType())} {kvp.Value};") + .ToList(); + var properties = _properties? + .ToDictionary(it => it.Name, + it => $"{TranslateNullable(it.Type, it.NullableContext ?? Definitions?.NullableContext, it.Nullable)} {it.Name} {{ get; {(it.IsReadOnly ? "" : it.IsInitOnly ? "init; " : "set; ")}}}"); + var ctorParams = _properties?.Where(it => it.IsReadOnly).ToList(); + if (Definitions?.TypeName != null) + { + if (_usings != null) + { + var namespaces = _usings + .OrderBy(it => it == "System" || it.StartsWith("System.") ? 0 : 1) + .ThenBy(it => it) + .ToList(); + foreach (var ns in namespaces) + { + WriteNextLine("using ", ns, ";"); + } + WriteLine(); + } + + // NOTE: type alias cannot solve all name conflicted case, user should use PrintFullTypeName + // keep logic here for compatability + if (_typeNames != null) + { + var names = _typeNames + .Where(kvp => !kvp.Value.Contains('.') && GetTypeName(kvp.Key) != kvp.Value) + .OrderBy(kvp => kvp.Value) + .ToList(); + foreach (var name in names) + { + WriteNextLine("using ", name.Value, " = ", name.Key.FullName!, ";"); + } + if (names.Count > 0) + WriteLine(); + } + + if (Definitions.Namespace != null) + { + WriteNextLine("namespace ", Definitions.Namespace); + Indent(); + } + + var isInternal = Definitions.IsInternal; + if (!isInternal) + isInternal = Definitions.Implements?.Any(it => + !it.GetTypeInfo().IsInterface && !it.GetTypeInfo().IsPublic) ?? false; + WriteModifierNextLine(isInternal ? "internal" : "public"); + Write("partial ", Definitions.IsRecordType ? "record " : "class ", Definitions.TypeName); + if (Definitions.IsRecordType && ctorParams?.Count > 0) + { + WriteCtorParams(ctorParams); + } + if (implements?.Any() == true) + { + Write(" : ", string.Join(", ", implements)); + } + Indent(); + } + if (constants != null) + { + foreach (var constant in constants) + { + WriteModifierNextLine("private"); + Write(constant); + } + WriteLine(); + } + if (_properties != null && Definitions?.TypeName != null) + { + foreach (var property in _properties) + { + if (Definitions.IsRecordType && property.IsReadOnly) + continue; + var isInternal = property.Type.GetTypeInfo().IsNotPublic; + WriteModifierNextLine(isInternal ? "internal" : "public"); + Write(properties![property.Name]); + } + WriteLine(); + + if (ctorParams?.Count > 0 && !Definitions.IsRecordType) + { + var isInternal = ctorParams.Any(it => it.Type.GetTypeInfo().IsNotPublic); + WriteModifierNextLine(isInternal ? "internal" : "public"); + Write(Definitions.TypeName); + WriteCtorParams(ctorParams); + Indent(); + foreach (var parameter in ctorParams) + { + WriteNextLine("this.", parameter.Name, " = ", char.ToLower(parameter.Name[0]).ToString(), parameter.Name.Substring(1), ";"); + } + Outdent(); + WriteLine(); + } + } + + var sb = _writer.GetStringBuilder(); + if (temp.GetStringBuilder().Length > 0) + { + _writer.Write(temp); + if (_appendWriters != null) + { + foreach (var item in _appendWriters) + { + _writer.Write(item); + } + } + } + else + { + sb.Length = sb.FindEndIndex(); + } + + if (Definitions?.TypeName != null) + { + Outdent(); + if (Definitions?.Namespace != null) + Outdent(); + } + + int wsCount = sb.FindStartIndex(); + return sb.ToString(wsCount, sb.Length - wsCount); + } + finally + { + _writer = temp; + _indentLevel = oldIndent; + } + } + + private void WriteCtorParams(List ctorParams) + { + Write("("); + for (var i = 0; i < ctorParams.Count; i++) + { + var parameter = ctorParams[i]; + if (i > 0) + Write(", "); + Write($"{TranslateNullable(parameter.Type, parameter.NullableContext ?? Definitions?.NullableContext, parameter.Nullable)} {char.ToLower(parameter.Name[0]) + parameter.Name.Substring(1)}"); + } + Write(")"); + } + + public enum LambdaType + { + PublicMethod, + PublicLambda, + PrivateMethod, + PrivateLambda, + ExtensionMethod, + } + } +} diff --git a/src/ExpressionTranslator/ExpressionTranslator.csproj b/src/ExpressionTranslator/ExpressionTranslator.csproj new file mode 100644 index 00000000..44eb22ac --- /dev/null +++ b/src/ExpressionTranslator/ExpressionTranslator.csproj @@ -0,0 +1,43 @@ + + + + net6.0 + True + Chaowlert Chaisrichalermpol + Translate from linq expressions to C# code + https://github.com/chaowlert/ExpressionDebugger + https://github.com/chaowlert/ExpressionDebugger + expression;linq;debug + https://cloud.githubusercontent.com/assets/5763993/26522656/41e28a6e-432f-11e7-9cae-7856f927d1a1.png + True + true + ExpressionTranslator.snk + 2.4.3 + ExpressionDebugger + MIT + icon.png + + 8.0 + enable + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ExpressionTranslator/ExpressionTranslator.snk b/src/ExpressionTranslator/ExpressionTranslator.snk new file mode 100644 index 00000000..b7f3c897 Binary files /dev/null and b/src/ExpressionTranslator/ExpressionTranslator.snk differ diff --git a/src/ExpressionTranslator/ExpressionTranslatorExtensions.cs b/src/ExpressionTranslator/ExpressionTranslatorExtensions.cs new file mode 100644 index 00000000..3ae64bf0 --- /dev/null +++ b/src/ExpressionTranslator/ExpressionTranslatorExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq.Expressions; + +namespace ExpressionDebugger +{ + public static class ExpressionTranslatorExtensions + { + /// + /// Generate script text + /// + public static string ToScript(this Expression node, ExpressionDefinitions? definitions = null) + { + var translator = ExpressionTranslator.Create(node, definitions); + return translator.ToString(); + } + } +} diff --git a/src/ExpressionTranslator/Extensions.cs b/src/ExpressionTranslator/Extensions.cs new file mode 100644 index 00000000..04ca7d7d --- /dev/null +++ b/src/ExpressionTranslator/Extensions.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ExpressionDebugger +{ + internal static class Extensions + { + public static HashSet ToHashSet(this IEnumerable source) + { + return new HashSet(source); + } + +#if NET40 + public static Type GetTypeInfo(this Type type) { + return type; + } +#endif + +#if NET40 || NETSTANDARD1_3 || NET6_0_OR_GREATER + public static T GetCustomAttribute(this MemberInfo memberInfo) where T : Attribute + { + return (T)memberInfo.GetCustomAttributes(typeof(T), true).SingleOrDefault(); + } + + public static T GetCustomAttribute(this Type type) where T : Attribute + { + return (T)type.GetTypeInfo().GetCustomAttributes(typeof(T), true).SingleOrDefault(); + } +#endif + + public static int FindStartIndex(this StringBuilder sb) + { + int wsCount = 0; + for (int i = 0; i < sb.Length; i++) + { + if (char.IsWhiteSpace(sb[i])) + wsCount++; + else + break; + } + return wsCount; + } + + public static int FindEndIndex(this StringBuilder sb) + { + int wsCount = 0; + for (int i = sb.Length - 1; i >= 0; i--) + { + if (char.IsWhiteSpace(sb[i])) + wsCount++; + else + break; + } + return sb.Length - wsCount; + } + } +} diff --git a/src/ExpressionTranslator/PropertyDefinitions.cs b/src/ExpressionTranslator/PropertyDefinitions.cs new file mode 100644 index 00000000..f393b246 --- /dev/null +++ b/src/ExpressionTranslator/PropertyDefinitions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace ExpressionDebugger +{ + public class PropertyDefinitions + { + public Type Type { get; set; } + public string Name { get; set; } + public bool IsReadOnly { get; set; } + public bool IsInitOnly { get; set; } + + /// + /// Set to 2 to mark type as nullable + /// + public byte? NullableContext { get; set; } + + /// + /// If type is generic, array or tuple, you can mark nullable for each type + /// Set to 2 for nullable + /// + public byte[]? Nullable { get; set; } + } +} diff --git a/src/ExpressionTranslator/TypeDefinitions.cs b/src/ExpressionTranslator/TypeDefinitions.cs new file mode 100644 index 00000000..a8e20667 --- /dev/null +++ b/src/ExpressionTranslator/TypeDefinitions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace ExpressionDebugger +{ + public class TypeDefinitions + { + public string? Namespace { get; set; } + public string? TypeName { get; set; } + public bool IsStatic { get; set; } + public bool IsInternal { get; set; } + public IEnumerable? Implements { get; set; } + public bool PrintFullTypeName { get; set; } + public bool IsRecordType { get; set; } + + /// + /// Set to 2 to mark all properties as nullable + /// + public byte? NullableContext { get; set; } + } +} \ No newline at end of file diff --git a/src/ExpressionTranslator/icon.png b/src/ExpressionTranslator/icon.png new file mode 100644 index 00000000..5202fb9a Binary files /dev/null and b/src/ExpressionTranslator/icon.png differ diff --git a/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj b/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj index 5f53b3c6..a73717fc 100644 --- a/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj +++ b/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/Mapster.Async/Mapster.Async.csproj b/src/Mapster.Async/Mapster.Async.csproj index c9ab50ac..e3615037 100644 --- a/src/Mapster.Async/Mapster.Async.csproj +++ b/src/Mapster.Async/Mapster.Async.csproj @@ -1,7 +1,7 @@ - netstandard1.3;netstandard2.0 + net6.0 Async supports for Mapster true Mapster;Async @@ -11,10 +11,10 @@ - + - + diff --git a/src/Mapster.Core/MapContext/MapContext.cs b/src/Mapster.Core/MapContext/MapContext.cs index afbcdf1a..6af8263d 100644 --- a/src/Mapster.Core/MapContext/MapContext.cs +++ b/src/Mapster.Core/MapContext/MapContext.cs @@ -15,7 +15,7 @@ namespace Mapster /// public class MapContext { -#if NETSTANDARD +#if NETSTANDARD || NET6_0_OR_GREATER private static readonly AsyncLocal _localContext = new AsyncLocal(); public static MapContext? Current { diff --git a/src/Mapster.Core/Mapster.Core.csproj b/src/Mapster.Core/Mapster.Core.csproj index fa729be2..702e8d02 100644 --- a/src/Mapster.Core/Mapster.Core.csproj +++ b/src/Mapster.Core/Mapster.Core.csproj @@ -1,7 +1,7 @@  Lightweight library for Mapster and Mapster CodeGen - netstandard2.0;netstandard1.3 + net6.0 Mapster.Core mapster 1.2.1-pre02 diff --git a/src/Mapster.DependencyInjection.Tests/InjectionTest.cs b/src/Mapster.DependencyInjection.Tests/InjectionTest.cs index c8f9a3aa..28bc635a 100644 --- a/src/Mapster.DependencyInjection.Tests/InjectionTest.cs +++ b/src/Mapster.DependencyInjection.Tests/InjectionTest.cs @@ -41,6 +41,8 @@ public void NoServiceAdapter_InjectionError() IServiceCollection sc = new ServiceCollection(); sc.AddScoped(); sc.AddSingleton(config); + // We should use ServiceMapper in normal code + // but for this test we want to be sure the code will generate the InvalidOperationException sc.AddScoped(); var sp = sc.BuildServiceProvider(); diff --git a/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj b/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj index 4fdb0900..5c136560 100644 --- a/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj +++ b/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj index 0f0ad0fd..8a4a1d3b 100644 --- a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj +++ b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0 + net6.0 Dependency Injection supports for Mapster true Mapster;DependencyInjection @@ -9,14 +9,13 @@ Mapster.DependencyInjection.snk 1.0.0 - - - - + + + diff --git a/src/Mapster.EF6/Mapster.EF6.csproj b/src/Mapster.EF6/Mapster.EF6.csproj index 46e129cb..b8c57b12 100644 --- a/src/Mapster.EF6/Mapster.EF6.csproj +++ b/src/Mapster.EF6/Mapster.EF6.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 EF6 plugin for Mapster true Mapster;EF6 @@ -11,11 +11,14 @@ 2.0.0 - - + + + + + \ No newline at end of file diff --git a/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj b/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj index 4d6f7a2b..411eaa44 100644 --- a/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj +++ b/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Mapster.EFCore/Mapster.EFCore.csproj b/src/Mapster.EFCore/Mapster.EFCore.csproj index 57204e78..77d618cd 100644 --- a/src/Mapster.EFCore/Mapster.EFCore.csproj +++ b/src/Mapster.EFCore/Mapster.EFCore.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 EFCore plugin for Mapster true Mapster;EFCore @@ -12,11 +12,14 @@ - - + + + + + \ No newline at end of file diff --git a/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj b/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj index 160fa92e..ffe876ac 100644 --- a/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj +++ b/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Mapster.Immutable/Mapster.Immutable.csproj b/src/Mapster.Immutable/Mapster.Immutable.csproj index 7435fc7d..a89af4d9 100644 --- a/src/Mapster.Immutable/Mapster.Immutable.csproj +++ b/src/Mapster.Immutable/Mapster.Immutable.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0 + net6.0 Immutable collection supports for Mapster true Mapster;Immutable @@ -14,8 +14,10 @@ - - + + + + diff --git a/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj b/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj index 32cc46c3..9411f47b 100644 --- a/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj +++ b/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/Mapster.JsonNet/Mapster.JsonNet.csproj b/src/Mapster.JsonNet/Mapster.JsonNet.csproj index 73cc22ff..49266168 100644 --- a/src/Mapster.JsonNet/Mapster.JsonNet.csproj +++ b/src/Mapster.JsonNet/Mapster.JsonNet.csproj @@ -1,7 +1,7 @@  - netstandard1.3;netstandard2.0 + net6.0 Json.net conversion supports for Mapster true Mapster;Json.net @@ -11,11 +11,14 @@ - + + + + diff --git a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj index 0695e8f9..883eb758 100644 --- a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj +++ b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 Source generator to generate mapping using Mapster source-generator;mapster true @@ -18,8 +18,8 @@ - - + + diff --git a/src/Mapster.Tests/Mapster.Tests.csproj b/src/Mapster.Tests/Mapster.Tests.csproj index 76f179cf..63eb3b6e 100644 --- a/src/Mapster.Tests/Mapster.Tests.csproj +++ b/src/Mapster.Tests/Mapster.Tests.csproj @@ -9,15 +9,15 @@ true - - - - + + + - + + diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index d081641e..5e6f0358 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -206,9 +206,14 @@ public void MapToNotVisibleInterfaceThrows() var ex = Should.Throw(() => dto.Adapt()); ex.InnerException.ShouldBeOfType(); - ex.InnerException.Message.ShouldContain("not accessible", "Correct InvalidOperationException must be thrown."); + // Not an expert in ShouldBe, so I had to change the code below + //ex.InnerException.Message.ShouldContain("", "Correct InvalidOperationException must be thrown."); + if (!ex.InnerException.Message.Contains("not accessible")) + { + throw new InvalidOperationException("Correct InvalidOperationException must be thrown."); + } } - + [TestMethod] public void MapToInheritedInterfaceWithoutProperties() { diff --git a/src/Mapster.Tool/Mapster.Tool.csproj b/src/Mapster.Tool/Mapster.Tool.csproj index 71e088e6..8e4c1b2f 100644 --- a/src/Mapster.Tool/Mapster.Tool.csproj +++ b/src/Mapster.Tool/Mapster.Tool.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1;net5.0;net6.0 + net6.0 true true dotnet-mapster @@ -20,11 +20,11 @@ - - + + diff --git a/src/Mapster.sln b/src/Mapster.sln index 2fd8a0cd..f5df1d5e 100644 --- a/src/Mapster.sln +++ b/src/Mapster.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29709.97 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A5580F9D-0F5F-4224-980F-7824536A627D}" ProjectSection(SolutionItems) = preProject @@ -55,6 +55,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mapster.SourceGenerator", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mapster.Core", "Mapster.Core\Mapster.Core.csproj", "{1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionDebugger", "ExpressionDebugger\ExpressionDebugger.csproj", "{7358E975-3588-424F-8859-8AFFEF5B4A1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionTranslator", "ExpressionTranslator\ExpressionTranslator.csproj", "{411B0A68-AA3B-441E-BC8C-CCE1FBE88AFA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemplateTest", "TemplateTest\TemplateTest.csproj", "{ED390145-FA22-46BA-86A6-9FA6AC869BA4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -137,6 +143,18 @@ Global {1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Release|Any CPU.Build.0 = Release|Any CPU + {7358E975-3588-424F-8859-8AFFEF5B4A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7358E975-3588-424F-8859-8AFFEF5B4A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7358E975-3588-424F-8859-8AFFEF5B4A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7358E975-3588-424F-8859-8AFFEF5B4A1E}.Release|Any CPU.Build.0 = Release|Any CPU + {411B0A68-AA3B-441E-BC8C-CCE1FBE88AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {411B0A68-AA3B-441E-BC8C-CCE1FBE88AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {411B0A68-AA3B-441E-BC8C-CCE1FBE88AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {411B0A68-AA3B-441E-BC8C-CCE1FBE88AFA}.Release|Any CPU.Build.0 = Release|Any CPU + {ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -156,6 +174,7 @@ Global {DE045991-6268-46EE-B5D3-79DE75820976} = {916FA044-B9E5-44F2-991A-85AA43C08255} {D5DF0FB7-44A5-4326-9EC4-5B8F7FCCE00F} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847} {3CB56440-5449-4DE5-A8D3-549C87C1B36A} = {EF7E343F-592E-4EAC-A0A4-92EB4B95CB89} + {ED390145-FA22-46BA-86A6-9FA6AC869BA4} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {83B87DBA-277C-49F1-B597-E3B78C2C8275} diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 20f83f32..d7588978 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -2,7 +2,9 @@ A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. - netstandard2.0;netstandard1.3 + Copyright (c) 2016 Chaowlert Chaisrichalermpol, Eric Swann + chaowlert;eric_swann + net6.0 Mapster A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. Mapster diff --git a/src/Mapster/Utils/CoreExtensions.cs b/src/Mapster/Utils/CoreExtensions.cs index 7e56ba5e..e5866402 100644 --- a/src/Mapster/Utils/CoreExtensions.cs +++ b/src/Mapster/Utils/CoreExtensions.cs @@ -25,9 +25,11 @@ public static void LockRemove(this List list, T item) } } +#if !NET6_0_OR_GREATER public static HashSet ToHashSet(this IEnumerable source) { return new HashSet(source); } +#endif } } diff --git a/src/Mapster/Utils/CustomAttributeUtil.cs b/src/Mapster/Utils/CustomAttributeUtil.cs index 4b487b5e..1cbaad1d 100644 --- a/src/Mapster/Utils/CustomAttributeUtil.cs +++ b/src/Mapster/Utils/CustomAttributeUtil.cs @@ -42,7 +42,7 @@ public static bool IsField(this CustomAttributeNamedArgument arg) #endif -#if NETSTANDARD1_3 +#if NETSTANDARD1_3 || NET6_0_OR_GREATER public static IEnumerable GetCustomAttributesData(this ParameterInfo parameter) { return parameter.CustomAttributes; diff --git a/src/Mapster/Utils/DynamicTypeGenerator.cs b/src/Mapster/Utils/DynamicTypeGenerator.cs index 0d36d59c..7e901417 100644 --- a/src/Mapster/Utils/DynamicTypeGenerator.cs +++ b/src/Mapster/Utils/DynamicTypeGenerator.cs @@ -101,7 +101,7 @@ private static Type CreateTypeForInterface(Type interfaceType) ctorIl.Emit(OpCodes.Ret); } -#if NETSTANDARD2_0 +#if NETSTANDARD2_0 || NET6_0_OR_GREATER return builder.CreateTypeInfo()!; #elif NETSTANDARD1_3 return builder.CreateTypeInfo().AsType(); diff --git a/src/Sample.AspNetCore/Controllers/SchoolController.cs b/src/Sample.AspNetCore/Controllers/SchoolController.cs index 096f58f6..7bd8e216 100644 --- a/src/Sample.AspNetCore/Controllers/SchoolController.cs +++ b/src/Sample.AspNetCore/Controllers/SchoolController.cs @@ -6,9 +6,9 @@ using Mapster; using Sample.AspNetCore.Models; using MapsterMapper; -using Microsoft.AspNet.OData; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.OData.Query; namespace Sample.AspNetCore.Controllers { diff --git a/src/Sample.AspNetCore/Sample.AspNetCore.csproj b/src/Sample.AspNetCore/Sample.AspNetCore.csproj index f2f49b18..9866b4a4 100644 --- a/src/Sample.AspNetCore/Sample.AspNetCore.csproj +++ b/src/Sample.AspNetCore/Sample.AspNetCore.csproj @@ -6,10 +6,10 @@ - - - - + + + + diff --git a/src/Sample.AspNetCore/Startup.cs b/src/Sample.AspNetCore/Startup.cs index 18d68e71..8b1ee9b8 100644 --- a/src/Sample.AspNetCore/Startup.cs +++ b/src/Sample.AspNetCore/Startup.cs @@ -5,12 +5,12 @@ using Sample.AspNetCore.Controllers; using Sample.AspNetCore.Models; using MapsterMapper; -using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.OData; namespace Sample.AspNetCore { @@ -27,6 +27,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddControllers(opts => opts.EnableEndpointRouting = false) + .AddOData(options => options.Select().Filter().OrderBy()) .AddNewtonsoftJson(); services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); @@ -34,7 +35,6 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddSingleton(); services.AddProblemDetails(); - services.AddOData(); } private static TypeAdapterConfig GetConfiguredMappingConfig() @@ -72,11 +72,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseProblemDetails(); app.UseRouting(); app.UseAuthorization(); - app.UseMvc(builder => - { - builder.EnableDependencyInjection(); - builder.Select().Expand().Filter().OrderBy().MaxTop(1000).Count(); - }); + app.UseMvc(); } } } diff --git a/src/Sample.CodeGen/Controllers/SchoolController.cs b/src/Sample.CodeGen/Controllers/SchoolController.cs index c94e2231..c9a588df 100644 --- a/src/Sample.CodeGen/Controllers/SchoolController.cs +++ b/src/Sample.CodeGen/Controllers/SchoolController.cs @@ -1,8 +1,8 @@ using System.Linq; using System.Threading.Tasks; using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNet.OData; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Query; using Microsoft.EntityFrameworkCore; using Sample.CodeGen.Domains; using Sample.CodeGen.Mappers; diff --git a/src/Sample.CodeGen/Properties/launchSettings.json b/src/Sample.CodeGen/Properties/launchSettings.json index da6fc229..5aa2546c 100644 --- a/src/Sample.CodeGen/Properties/launchSettings.json +++ b/src/Sample.CodeGen/Properties/launchSettings.json @@ -11,6 +11,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "launchUrl": "school/course", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -18,6 +19,7 @@ "Sample.CodeGen": { "commandName": "Project", "launchBrowser": true, + "launchUrl": "school/course", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/Sample.CodeGen/Sample.CodeGen.csproj b/src/Sample.CodeGen/Sample.CodeGen.csproj index f63cee31..03a79b04 100644 --- a/src/Sample.CodeGen/Sample.CodeGen.csproj +++ b/src/Sample.CodeGen/Sample.CodeGen.csproj @@ -6,12 +6,11 @@ - - - - - - + + + + + @@ -27,6 +26,9 @@ + + + PreserveNewest diff --git a/src/Sample.CodeGen/Startup.cs b/src/Sample.CodeGen/Startup.cs index f74f1008..8b0cad18 100644 --- a/src/Sample.CodeGen/Startup.cs +++ b/src/Sample.CodeGen/Startup.cs @@ -1,7 +1,7 @@ using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.OData; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -23,11 +23,11 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddControllers(opts => opts.EnableEndpointRouting = false) + .AddOData(options => options.Select().Filter().OrderBy()) .AddNewtonsoftJson(); services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddProblemDetails(); - services.AddOData(); services.Scan(selector => selector.FromCallingAssembly() .AddClasses().AsMatchingInterface().WithSingletonLifetime()); } @@ -38,11 +38,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseProblemDetails(); app.UseRouting(); app.UseAuthorization(); - app.UseMvc(builder => - { - builder.EnableDependencyInjection(); - builder.Select().Expand().Filter().OrderBy().MaxTop(1000).Count(); - }); + app.UseMvc(); } } } diff --git a/src/TemplateTest/CreateMapExpressionTest.cs b/src/TemplateTest/CreateMapExpressionTest.cs new file mode 100644 index 00000000..4929bb12 --- /dev/null +++ b/src/TemplateTest/CreateMapExpressionTest.cs @@ -0,0 +1,105 @@ +using ExpressionDebugger; +using Mapster; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace TemplateTest +{ + [TestClass] + public class CreateMapExpressionTest + { + [TestMethod] + public void TestCreateMapExpression() + { + TypeAdapterConfig.GlobalSettings.SelfContainedCodeGeneration = true; + var foo = default(Customer); + var def = new ExpressionDefinitions + { + IsStatic = true, + MethodName = "Map", + Namespace = "Benchmark", + TypeName = "CustomerMapper" + }; + var code = foo.BuildAdapter() + .CreateMapExpression() + .ToScript(def); + + Assert.IsNotNull(code); + } + + [TestMethod] + public void TestCreateMapToTargetExpression() + { + TypeAdapterConfig.GlobalSettings.SelfContainedCodeGeneration = true; + var foo = default(Customer); + var def = new ExpressionDefinitions + { + IsStatic = true, + MethodName = "Map", + Namespace = "Benchmark", + TypeName = "CustomerMapper" + }; + var code = foo.BuildAdapter() + .CreateMapToTargetExpression() + .ToScript(def); + + Assert.IsNotNull(code); + } + + [TestMethod] + public void TestCreateProjectionExpression() + { + TypeAdapterConfig.GlobalSettings.SelfContainedCodeGeneration = true; + var foo = default(Customer); + var def = new ExpressionDefinitions + { + IsStatic = true, + MethodName = "Map", + Namespace = "Benchmark", + TypeName = "CustomerMapper" + }; + var code = foo.BuildAdapter() + .CreateProjectionExpression() + .ToScript(def); + + Assert.IsNotNull(code); + } + } + + public class Address + { + public int Id { get; set; } + public string Street { get; set; } + public string City { get; set; } + public string Country { get; set; } + } + + public class AddressDTO + { + public int Id { get; set; } + public string City { get; set; } + public string Country { get; set; } + } + + public class Customer + { + public int Id { get; set; } + public string Name { get; set; } + public decimal? Credit { get; set; } + public Address Address { get; set; } + public Address HomeAddress { get; set; } + public Address[] Addresses { get; set; } + public ICollection
WorkAddresses { get; set; } + } + + public class CustomerDTO + { + public int Id { get; set; } + public string Name { get; set; } + public AddressDTO Address { get; set; } + public AddressDTO HomeAddress { get; set; } + public AddressDTO[] Addresses { get; set; } + public List WorkAddresses { get; set; } + public string AddressCity { get; set; } + } +} \ No newline at end of file diff --git a/src/TemplateTest/FooTest.cs b/src/TemplateTest/FooTest.cs new file mode 100644 index 00000000..942bf4f8 --- /dev/null +++ b/src/TemplateTest/FooTest.cs @@ -0,0 +1,59 @@ +using ExpressionDebugger; +using Mapster; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; + +namespace TemplateTest +{ + [TestClass] + public class FooTest + { + [TestMethod] + public void TestCreateMapExpression() + { + TypeAdapterConfig.GlobalSettings.SelfContainedCodeGeneration = true; + var foo = default(Foo); + var def = new ExpressionDefinitions + { + IsStatic = true, + MethodName = "Map", + Namespace = "Benchmark", + TypeName = "FooMapper" + }; + var code = foo.BuildAdapter() + .CreateMapExpression() + .ToScript(def); + code = code.Replace("TypeAdapter.Map.Invoke", "Map"); + + Assert.IsNotNull(code); + } + } + + public class Foo + { + public string Name { get; set; } + + public int Int32 { get; set; } + + public long Int64 { set; get; } + + public int? NullInt { get; set; } + + public float Floatn { get; set; } + + public double Doublen { get; set; } + + public DateTime DateTime { get; set; } + + public Foo Foo1 { get; set; } + + public IEnumerable Foos { get; set; } + + public Foo[] FooArr { get; set; } + + public int[] IntArr { get; set; } + + public IEnumerable Ints { get; set; } + } +} \ No newline at end of file diff --git a/src/TemplateTest/TemplateTest.csproj b/src/TemplateTest/TemplateTest.csproj new file mode 100644 index 00000000..34af174b --- /dev/null +++ b/src/TemplateTest/TemplateTest.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + +