├── .editorconfig ├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── .hgignore ├── .hgtags ├── .nuget └── NuGet.Config ├── ExpressionToCode.Benchmarks ├── ExpressionToCode.Benchmarks.csproj └── Program.cs ├── ExpressionToCode.lutconfig ├── ExpressionToCode.sln ├── ExpressionToCode.sln.DotSettings ├── ExpressionToCodeLib ├── CodeAnnotators.cs ├── ExpressionExpectations.cs ├── ExpressionToCode.cs ├── ExpressionToCodeConfiguration.cs ├── ExpressionToCodeLib.csproj ├── ExpressionTreeAssertion.cs ├── ExpressionTreeCompilers.cs ├── FormatStringParser.cs ├── GlobalUsings.cs ├── IObjectStringifier.cs ├── Internal │ ├── DotnetExpressionCompiler.cs │ ├── EqualityExpressions.cs │ ├── ExpressionPrecedence.cs │ ├── ExpressionToCodeImpl.cs │ ├── ExpressionToCodeString.cs │ ├── ExpressionTypeDispatch.Generated.cs │ ├── ExpressionTypeDispatch.tt │ ├── FastExpressionCompilerImpl.cs │ ├── NullabilityHelpers.cs │ ├── ObjectStringifyImpl.cs │ ├── ObjectToCodeImpl.cs │ ├── ReflectionHelpers.cs │ ├── StringifiedExpression.cs │ ├── SubExpressionPerLineCodeAnnotator.cs │ ├── TypeToCodeConfig.cs │ └── ValuesOnStalksCodeAnnotator.cs ├── ObjectStringify.cs ├── ObjectToCode.cs └── PAssert.cs ├── ExpressionToCodeTest ├── AnnotatedToCodeTest.cs ├── AnonymousObjectFormattingTest.AnonymousObjectsInArrayExpression.approved.txt ├── AnonymousObjectFormattingTest.MessyEnumerablesOfAnonymousObjects.approved.txt ├── AnonymousObjectFormattingTest.cs ├── ApiStabilityTest.PublicApi.approved.txt ├── ApiStabilityTest.UnstableApi.approved.txt ├── ApiStabilityTest.cs ├── ApprovalTest.cs ├── ArrayAccessTests.cs ├── BlockExpressionTest.cs ├── CSharpFriendlyTypeNameTest.cs ├── EnumTests.cs ├── EnumerableFormattingTest.NestedArraysUseProperConfig.approved.txt ├── EnumerableFormattingTest.cs ├── EqualityComparingTest.cs ├── ExceptionsSerialization.cs ├── ExpressionInterpretationBug.cs ├── ExpressionToCodeLibTest.cs ├── ExpressionToCodeTest.csproj ├── ExpressionWithNameTest.cs ├── GenericsTestClasses.cs ├── GlobalUsings.cs ├── ImplicitCastingTest.cs ├── JunkClassesForTesting.cs ├── MultipleIndexerTest.cs ├── NestedClassTest.cs ├── PAssertTest.cs ├── StringInterpolationTest.cs ├── SubExprExceptionTest.cs ├── SubExpressionPerLineCodeAnnotatorTest.A_plus_B_approved.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.Binary_expressions_with_nesting.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.DealsOkWithEnumerablesOfAnonymousObjects.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.DealsOkWithLongEnumerables.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.DealsOkWithLongStrings.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.DealsOkWithObjectsContainingLongMultilineStrings.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.DealsOkWithObjectsContainingLongStrings.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.MessyStructureElidesNeatly.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.MethodCallsAndArrayLiterals.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.NestedArrayAccess.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.NestedArrayAccessWithOuterAnd.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.NodesThatAreUndescribableAreNotDescribed.approved.txt ├── SubExpressionPerLineCodeAnnotatorTest.cs ├── ToValuedCodeTest.cs ├── TopLevelProgramTest.cs ├── ValueOnStalksAnnotatorTest.A_plus_B_approved.approved.txt ├── ValueOnStalksAnnotatorTest.Binary_expressions_with_nesting.approved.txt ├── ValueOnStalksAnnotatorTest.DealsOkWithEnumerablesOfAnonymousObjects.approved.txt ├── ValueOnStalksAnnotatorTest.DealsOkWithLongEnumerables.approved.txt ├── ValueOnStalksAnnotatorTest.DealsOkWithLongStrings.approved.txt ├── ValueOnStalksAnnotatorTest.DealsOkWithObjectsContainingLongMultilineStrings.approved.txt ├── ValueOnStalksAnnotatorTest.DealsOkWithObjectsContainingLongStrings.approved.txt ├── ValueOnStalksAnnotatorTest.MessyStructureElidesNeatly.approved.txt ├── ValueOnStalksAnnotatorTest.MethodCallsAndArrayLiterals.approved.txt ├── ValueOnStalksAnnotatorTest.NestedArrayAccess.approved.txt ├── ValueOnStalksAnnotatorTest.NestedArrayAccessWithOuterAnd.approved.txt ├── ValueOnStalksAnnotatorTest.NodesThatAreUndescribableAreNotDescribed.approved.txt ├── ValueOnStalksAnnotatorTest.cs └── ValueTupleTests.cs ├── LICENSE ├── README.md ├── TopLevelProgramExample ├── TopLevelProgram.cs ├── TopLevelProgramExample.csproj └── TopLevelProgramMarker.cs └── how-to-push-to-nuget.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cs] 4 | 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | tab_width = 4 11 | 12 | csharp_indent_labels = one_less_than_current 13 | csharp_new_line_before_catch = false 14 | csharp_new_line_before_else = false 15 | csharp_new_line_before_finally = false 16 | csharp_new_line_before_open_brace = methods,types,properties 17 | csharp_prefer_braces = true:error 18 | csharp_prefer_simple_default_expression = false:suggestion 19 | csharp_prefer_simple_using_statement = true:warning 20 | csharp_prefer_static_local_function = true:warning 21 | csharp_preserve_single_line_statements = false 22 | csharp_space_around_binary_operators = before_and_after 23 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 24 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 25 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 26 | csharp_style_conditional_delegate_call = true:suggestion 27 | csharp_style_deconstructed_variable_declaration = true:none 28 | csharp_style_expression_bodied_accessors = true:warning 29 | csharp_style_expression_bodied_constructors = true:warning 30 | csharp_style_expression_bodied_indexers = true:warning 31 | csharp_style_expression_bodied_lambdas = true:warning 32 | csharp_style_expression_bodied_local_functions = true:warning 33 | csharp_style_expression_bodied_methods = when_on_single_line:warning 34 | csharp_style_expression_bodied_operators = true:warning 35 | csharp_style_expression_bodied_properties = true:warning 36 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning 37 | csharp_style_inlined_variable_declaration = true:warning 38 | csharp_style_namespace_declarations = file_scoped:warning 39 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 40 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 41 | csharp_style_prefer_extended_property_pattern = true:warning 42 | csharp_style_prefer_index_operator = false:warning #due to netstandard2.0 limitation 43 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 44 | csharp_style_prefer_method_group_conversion = true:suggestion 45 | csharp_style_prefer_not_pattern = true:warning 46 | csharp_style_prefer_null_check_over_type_check = true:suggestion 47 | csharp_style_prefer_pattern_matching = true:suggestion 48 | csharp_style_prefer_range_operator = false:warning #alas unsupported in netstandard2.0 49 | csharp_style_prefer_switch_expression = true:warning 50 | csharp_style_prefer_top_level_statements = true:warning 51 | csharp_style_prefer_tuple_swap = true:warning 52 | csharp_style_prefer_utf8_string_literals = true:suggestion 53 | csharp_style_throw_expression = true:warning 54 | csharp_style_unused_value_assignment_preference = discard_variable:warning 55 | csharp_style_unused_value_expression_statement_preference = discard_variable:warning 56 | csharp_style_var_elsewhere = true:warning 57 | csharp_style_var_for_built_in_types = true:warning 58 | csharp_style_var_when_type_is_apparent = true:warning 59 | csharp_using_directive_placement = outside_namespace:warning 60 | dotnet_code_quality_unused_parameters = all:suggestion 61 | dotnet_diagnostic.IDE0079.severity = none 62 | dotnet_sort_system_directives_first = true 63 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 64 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 65 | dotnet_style_coalesce_expression = true:warning 66 | dotnet_style_collection_initializer = true:warning 67 | dotnet_style_explicit_tuple_names = true:warning 68 | dotnet_style_namespace_match_folder = true:warning 69 | dotnet_style_null_propagation = true:warning 70 | dotnet_style_object_initializer = true:warning 71 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 72 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:warning 73 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:warning 74 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning 75 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:warning 76 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 77 | dotnet_style_predefined_type_for_member_access = true:warning 78 | dotnet_style_prefer_auto_properties = true:warning 79 | dotnet_style_prefer_compound_assignment = true:warning 80 | dotnet_style_prefer_conditional_expression_over_assignment = true:warning 81 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 82 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 83 | dotnet_style_prefer_inferred_tuple_names = true:warning 84 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 85 | dotnet_style_prefer_simplified_boolean_expressions = true:warning 86 | dotnet_style_prefer_simplified_interpolation = true:warning 87 | dotnet_style_readonly_field = true:warning 88 | dotnet_style_require_accessibility_modifiers = never:warning 89 | 90 | 91 | dotnet_diagnostic.CS0612.severity = none #temporarily suppress obsoletion warnings. -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | packages 3 | bin 4 | obj 5 | Debug 6 | Release 7 | .vs 8 | 9 | *.suo 10 | 11 | # NCrunch 12 | *[_.][Nn][Cc]runch* 13 | *.orig 14 | *.nupkg 15 | *.received.txt 16 | *.csproj.user 17 | *.user 18 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | glob:*.suo 2 | glob:bin/ 3 | glob:obj/ 4 | glob:_ReSharper.* 5 | glob:*.user 6 | syntax: glob 7 | packages/ 8 | ExpressionToCodeLib/*.nupkg 9 | *.approved.txt.bak 10 | *.received.txt 11 | TestResults/**.coverage 12 | *.orig 13 | ExpressionToCode.Benchmarks/BenchmarkDotNet.Artifacts 14 | .vs 15 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 618c09999a63b8ad2bd79b82ae990a9d9efc39a9 1.0 2 | 4869f6351f6909b8835424cd2af24ad324845212 1.0.1 3 | 9cebc3c2969026bea279b845c95a17c59b48ff9a 1.0.2 4 | d79e78a341409da2ad0f301df54cf7b4e5934db6 1.02 5 | 10b7bc974f5be2deede0711788bcf4da64c9bb4a 1.0.3 6 | a9c02233659752118df2385552b35180b4cabdfc 1.0.4 7 | 11e5aab332a55575b03159380cbc7c4258e46d22 1.0.5 8 | a4fc16d634a668c0d931b6a10af4f7dd3354132f 1.1.0.1 9 | a5463bad845c8536cfb8b88db87b9947e2a2e992 1.2.0.0 10 | 2c41f538a7704ce925b1de2f15150ce726c7efcd 1.3.0 11 | b132625b504f867f948ca8903a410b1c2eb2530b 1.4 12 | 19f22d5e40ffc8dfd9bae6b38e157970276b8db4 1.4.1 13 | 4eadd7d573917795f61b63d5df4e79ce6a254e56 1.4.2 14 | 5443a6bcde98c728e3af725012b1afd02f3437f4 1.4.3 15 | d996b83d9763b64bbb7041e2603ff358ded85244 1.4.4 16 | ccd6ed5f91decc2fed5824ea80f46d284b399b5b 1.4.5 17 | 8238490b9129d245e37cb1ce0f3cdba8ee9472ed 1.4.6 18 | 6aa15b23e0282f09c4dff712017c088930d7527b 1.4.7 19 | 725b22e28cf8697542416844719655dcf8ebcae8 1.4.8 20 | 23c21f19c04f3510eaad9f6a7f6532cd3ce59d1f 1.5.0 21 | dbfe9ca3730d994b1f8028ff6b8b5ddeffcaf533 1.5.1 22 | d808f81b91303bf3e15adffd06c89b9a8d6d3d89 1.5.2 23 | fbc11ae79655423e5acf0d9848679b39ef1f16f5 1.5.3 24 | 85200876c65ffc86ae32d8d541e929e5f852e6dd 1.5.4 25 | 09c66e00b53055a523ba7e6f8074cf97e4c904e7 1.5.3 26 | 09c66e00b53055a523ba7e6f8074cf97e4c904e7 1.5.3 27 | 4400bddaf971fcef72ef8b3892a59a4cf7841020 1.5.4 28 | 4400bddaf971fcef72ef8b3892a59a4cf7841020 1.5.4 29 | 4f531fd6bfa98a1b3a125d45756bb7f10d9a1522 2.0.0-alpha3 30 | 9730cf51b73762fa94f0d424616a091b929395d7 2.0.0-alpha4 31 | a9fed6418983a5a0ec0095380edfd65e8cb64f8e 2.0.0-alpha6 32 | 96477826e473d03362418256e036d1cbe1625ae7 2.0.0-beta6 33 | 4f345dbcff550272d1ec39ba4eb2104bfcb531d1 2.0.0 34 | 5f0827ad1b71400462fa15b5f48d6d38e2a2d33a 2.1.0 35 | f0d163e6040d3215d22f1b3f9471436beee21ddd 2.2.0 36 | 1f9477f11b4f545b6c6570adf9bacbbb3db7c6d0 2.3.0 37 | e3edca0ccf8152c76d5c8aa0a83e11bef28364ae 2.4.0 38 | af4940a0bb35e4a40a5c9e3e580a2b72551188f0 2.4.1 39 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpressionToCode.Benchmarks/ExpressionToCode.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Maksim Volkau, Eamon Nerbonne 4 | net8.0 5 | Eamon Nerbonne 6 | Exe 7 | latest 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ExpressionToCode.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ExpressionToCodeLib; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using BenchmarkDotNet.Attributes; 7 | using BenchmarkDotNet.Running; 8 | using JetBrains.Annotations; 9 | 10 | namespace ExpressionToCode.Benchmarks; 11 | 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | => BenchmarkRunner.Run(); 16 | } 17 | 18 | [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] 19 | public class BenchmarkPAssert 20 | { 21 | static readonly ExpressionToCodeConfiguration 22 | baseLineConfiguration = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithCompiler(ExpressionTreeCompilers.DotnetExpressionCompiler), 23 | withOptimizationConfiguration = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithCompiler(ExpressionTreeCompilers.FastExpressionCompiler); 24 | 25 | readonly Expression> testExpr = GetExpression(); 26 | 27 | static Expression> GetExpression() 28 | { 29 | var x = 1; 30 | var s = "Test"; 31 | return () => x == 1 && (s.Contains("S") || s.Contains("s")); 32 | } 33 | 34 | static Func GetFunc() 35 | { 36 | var x = 1; 37 | var s = "Test"; 38 | return () => x == 1 && (s.Contains("S") || s.Contains("s")); 39 | } 40 | 41 | static void RunAssertion(Func assertion) 42 | { 43 | if (!assertion()) { 44 | throw new Exception(); 45 | } 46 | } 47 | 48 | [Benchmark] 49 | public void JustCompile() 50 | => testExpr.Compile(); 51 | 52 | [Benchmark] 53 | public void CompileAndRun() 54 | => RunAssertion(testExpr.Compile()); 55 | 56 | [Benchmark] 57 | public void JustFastCompile() 58 | => ExpressionTreeCompilers.FastExpressionCompiler.Compile(testExpr); 59 | 60 | [Benchmark] 61 | public void FastCompileAndRun() 62 | => RunAssertion(ExpressionTreeCompilers.FastExpressionCompiler.Compile(testExpr)); 63 | 64 | [Benchmark] 65 | public void PAssertWithCompile() 66 | => baseLineConfiguration.Assert(GetExpression()); 67 | 68 | [Benchmark] 69 | public void PAssertWithFastCompile() 70 | => withOptimizationConfiguration.Assert(GetExpression()); 71 | 72 | [Benchmark] 73 | public void BaseLinePlainLambdaExec() 74 | => RunAssertion(GetFunc()); 75 | } -------------------------------------------------------------------------------- /ExpressionToCode.lutconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 180000 6 | -------------------------------------------------------------------------------- /ExpressionToCode.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30803.129 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionToCodeLib", "ExpressionToCodeLib\ExpressionToCodeLib.csproj", "{2472FFFC-E2F9-4043-8CA6-EB65D4059A0F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionToCodeTest", "ExpressionToCodeTest\ExpressionToCodeTest.csproj", "{404475DF-91DC-40B2-A933-1C4A3C9B7710}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CA5B9979-2CAD-4D93-9591-128F5F8BA053}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | how-to-push-to-nuget.txt = how-to-push-to-nuget.txt 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{3FA226A6-D138-487E-96FB-41FFACBD74B8}" 19 | ProjectSection(SolutionItems) = preProject 20 | .nuget\NuGet.Config = .nuget\NuGet.Config 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionToCode.Benchmarks", "ExpressionToCode.Benchmarks\ExpressionToCode.Benchmarks.csproj", "{DE1DC107-D465-46B6-9198-5BB887E91619}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TopLevelProgramExample", "TopLevelProgramExample\TopLevelProgramExample.csproj", "{12DDCFE5-62AE-4795-86A4-BBE8C15DA908}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {2472FFFC-E2F9-4043-8CA6-EB65D4059A0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {2472FFFC-E2F9-4043-8CA6-EB65D4059A0F}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {2472FFFC-E2F9-4043-8CA6-EB65D4059A0F}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {2472FFFC-E2F9-4043-8CA6-EB65D4059A0F}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {404475DF-91DC-40B2-A933-1C4A3C9B7710}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {404475DF-91DC-40B2-A933-1C4A3C9B7710}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {404475DF-91DC-40B2-A933-1C4A3C9B7710}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {404475DF-91DC-40B2-A933-1C4A3C9B7710}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {DE1DC107-D465-46B6-9198-5BB887E91619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {DE1DC107-D465-46B6-9198-5BB887E91619}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {DE1DC107-D465-46B6-9198-5BB887E91619}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {DE1DC107-D465-46B6-9198-5BB887E91619}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {12DDCFE5-62AE-4795-86A4-BBE8C15DA908}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {12DDCFE5-62AE-4795-86A4-BBE8C15DA908}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {12DDCFE5-62AE-4795-86A4-BBE8C15DA908}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {12DDCFE5-62AE-4795-86A4-BBE8C15DA908}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {AE4D304A-FE54-49CE-8998-CBD4E9757CDA} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/CodeAnnotators.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class CodeAnnotators 4 | { 5 | /// 6 | /// This code annotator places sub-expression values below the stringified expression and connects the value to the 7 | /// appropriate 8 | /// place in the expression with an ascii-art line. It works reasonably well for short expression, but large 9 | /// expressions are unwieldy and hard to read. 10 | /// This works best in monospace fonts. 11 | /// 12 | public static readonly ICodeAnnotator ValuesOnStalksCodeAnnotator = new ValuesOnStalksCodeAnnotator(); 13 | 14 | /// 15 | /// This code annotator summarizes sub-expression values on separate lines below the stringified expression. 16 | /// This works reasonably well even for large expressions because the sub-expressions are represented compactly and 17 | /// there's no need for overal alignment. 18 | /// 19 | public static readonly ICodeAnnotator SubExpressionPerLineCodeAnnotator = new SubExpressionPerLineCodeAnnotator(); 20 | } 21 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionExpectations.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | /// 4 | /// Intended to be used as a static import; i.e. via "using static ExpressionToCodeLib.ExpressionExpectations;" 5 | /// 6 | public static class ExpressionExpectations 7 | { 8 | /// 9 | /// Evaluates an assertion and throws an exception the assertion it returns false or throws an exception. 10 | /// The exception includes the code of the assertion annotated with runtime values for its sub-expressions. 11 | /// This is identical to PAssert.That(()=>...). 12 | /// If you want to change the layout of the value annotations, see 13 | /// ExpressionToCodeConfiguration.GlobalAssertionConfiguration 14 | /// 15 | public static void Expect(Expression> assertion, string? msg = null) 16 | => ExpressionToCodeConfiguration.GlobalAssertionConfiguration.Assert(assertion, msg); 17 | } 18 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionToCode.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedMember.Global 2 | // ReSharper disable MemberCanBePrivate.Global 3 | 4 | namespace ExpressionToCodeLib; 5 | 6 | /// 7 | /// If you wish to override some formatting aspects of these methods, set 8 | /// ExpressionToCodeConfiguration.GlobalCodeGetConfiguration. 9 | /// 10 | public static class ExpressionToCode 11 | { 12 | public static string ToCode(Expression> e) 13 | => ToCode((Expression)e); 14 | 15 | public static string ToCode(Expression> e) 16 | => ToCode((Expression)e); 17 | 18 | public static string ToCode(Expression> e) 19 | => ToCode((Expression)e); 20 | 21 | public static string ToCode(Expression> e) 22 | => ToCode((Expression)e); 23 | 24 | public static string ToCode(Expression e) 25 | => ExpressionToCodeConfiguration.GlobalCodeGenConfiguration.GetExpressionToCode().ToCode(e); 26 | 27 | public static string AnnotatedToCode(Expression> e) 28 | => AnnotatedToCode((Expression)e); 29 | 30 | public static string AnnotatedToCode(Expression> e) 31 | => AnnotatedToCode((Expression)e); 32 | 33 | public static string AnnotatedToCode(Expression> e) 34 | => AnnotatedToCode((Expression)e); 35 | 36 | public static string AnnotatedToCode(Expression> e) 37 | => AnnotatedToCode((Expression)e); 38 | 39 | public static string AnnotatedToCode(Expression expr) 40 | => AnnotatedToCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, expr); 41 | 42 | public static string ToCode(this ExpressionToCodeConfiguration config, Expression> e) 43 | => config.GetExpressionToCode().ToCode(e); 44 | 45 | public static string ToCode(this ExpressionToCodeConfiguration config, Expression> e) 46 | => config.GetExpressionToCode().ToCode(e); 47 | 48 | public static string ToCode(this ExpressionToCodeConfiguration config, Expression> e) 49 | => config.GetExpressionToCode().ToCode(e); 50 | 51 | public static string ToCode(this ExpressionToCodeConfiguration config, Expression> e) 52 | => config.GetExpressionToCode().ToCode(e); 53 | 54 | public static string ToCode(this ExpressionToCodeConfiguration config, Expression e) 55 | => config.GetExpressionToCode().ToCode(e); 56 | 57 | public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression> e) 58 | => AnnotatedToCode(config, (Expression)e); 59 | 60 | public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression> e) 61 | => AnnotatedToCode(config, (Expression)e); 62 | 63 | public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression> e) 64 | => AnnotatedToCode(config, (Expression)e); 65 | 66 | public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression> e) 67 | => AnnotatedToCode(config, (Expression)e); 68 | 69 | internal static bool ShouldIgnoreSpaceAfter(char c) 70 | => c == ' ' || c == '('; 71 | 72 | public static string AnnotatedToCode(this ExpressionToCodeConfiguration config, Expression expr) 73 | => config.CodeAnnotator.AnnotateExpressionTree(config, expr, null, false); 74 | 75 | /// 76 | /// Converts expression to variable/property/method C# like representation adding it's string value. 77 | /// 78 | /// 79 | /// string toNameValueRepresentation = "Value"; 80 | /// ToRepr(() => toNameValueRepresentation); // "toNameValueRepresentation = Value" 81 | /// 82 | /// 83 | /// Unlike (which targets compilable output), this method is geared 84 | /// towards dumping simple objects into text, so may skip some C# issues for sake of readability. 85 | /// 86 | public static string ToValuedCode(this Expression> expression) 87 | { 88 | TResult retValue; 89 | try { 90 | retValue = ExpressionToCodeConfiguration.GlobalAssertionConfiguration.ExpressionCompiler.Compile(expression)(); 91 | } catch (Exception ex) { 92 | throw new InvalidOperationException("Cannon get return value of expression when it throws error", ex); 93 | } 94 | 95 | return ToCode(expression.Body) + " = " + retValue; 96 | } 97 | 98 | /// 99 | /// Gets property, variable or method name from lambda expression. 100 | /// 101 | /// Simple expression to obtain name from. 102 | /// The `name` of expression. 103 | /// 104 | /// var example = "some text"; 105 | /// var name = ExpressionToCode.GetNameIn(() => example); // "example" 106 | /// 107 | /// Unsupported or unknown or complex expression to get `name` of it. 108 | public static string GetNameIn(Expression> expression) 109 | => GetNameIn((Expression)expression); 110 | 111 | public static string GetNameIn(Expression expression) 112 | => GetNameIn((Expression)expression); 113 | 114 | public static string GetNameIn(Expression expr) 115 | { 116 | if (expr is MethodCallExpression callExpr) { 117 | return callExpr.Method.Name; 118 | } 119 | 120 | if (expr is MemberExpression accessExpr) { 121 | return accessExpr.Member.Name; 122 | } 123 | 124 | if (expr.NodeType == ExpressionType.ArrayLength) { 125 | return "Length"; 126 | } 127 | 128 | if (expr is LambdaExpression lambdaExpr) { 129 | return GetNameIn(lambdaExpr.Body); 130 | } 131 | 132 | if (expr is UnaryExpression unary) { 133 | return GetNameIn(unary.Operand); 134 | } 135 | 136 | throw new ArgumentException("Unsupported or unknown or complex expression to get `name` of it", nameof(expr)); 137 | } 138 | } 139 | 140 | public interface IExpressionToCode 141 | { 142 | string ToCode(Expression e); 143 | } 144 | 145 | public static class ExpressionToCodeExtensions 146 | { 147 | public static string ToCode(this IExpressionToCode it, Expression> e) 148 | => it.ToCode(e); 149 | 150 | public static string ToCode(this IExpressionToCode it, Expression> e) 151 | => it.ToCode(e); 152 | 153 | public static string ToCode(this IExpressionToCode it, Expression> e) 154 | => it.ToCode(e); 155 | 156 | public static string ToCode(this IExpressionToCode it, Expression> e) 157 | => it.ToCode(e); 158 | } 159 | 160 | public interface IAnnotatedToCode 161 | { 162 | string AnnotatedToCode(Expression e, string? msg, bool outerValueIsAssertionFailure); 163 | } 164 | 165 | public static class AnnotatedToCodeExtensions 166 | { 167 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression e) 168 | => it.AnnotatedToCode(e, null, false); 169 | 170 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e) 171 | => it.AnnotatedToCode(e, null, false); 172 | 173 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e) 174 | => it.AnnotatedToCode(e, null, false); 175 | 176 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e) 177 | => it.AnnotatedToCode(e, null, false); 178 | 179 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e) 180 | => it.AnnotatedToCode(e, null, false); 181 | 182 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e, string? msg, bool outerValueIsAssertionFailure) 183 | // ReSharper disable once RedundantCast 184 | => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure); 185 | 186 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e, string? msg, bool outerValueIsAssertionFailure) 187 | // ReSharper disable once RedundantCast 188 | => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure); 189 | 190 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e, string? msg, bool outerValueIsAssertionFailure) 191 | // ReSharper disable once RedundantCast 192 | => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure); 193 | 194 | public static string AnnotatedToCode(this IAnnotatedToCode it, Expression> e, string? msg, bool outerValueIsAssertionFailure) 195 | // ReSharper disable once RedundantCast 196 | => it.AnnotatedToCode((Expression)e, msg, outerValueIsAssertionFailure); 197 | } 198 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionToCodeConfiguration.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | 3 | namespace ExpressionToCodeLib; 4 | 5 | /// 6 | /// Specifies details of how expressions and their values are to be formatted. This object is immutable; all instance 7 | /// methods are thread safe. 8 | /// Changes to configuration return new configuration instances. 9 | /// 10 | public sealed record ExpressionToCodeConfiguration 11 | { 12 | public ICodeAnnotator CodeAnnotator { get; init; } = CodeAnnotators.SubExpressionPerLineCodeAnnotator; 13 | public IExpressionCompiler ExpressionCompiler { get; init; } = ExpressionTreeCompilers.DotnetExpressionCompiler; 14 | 15 | [Obsolete("Replaced by UseFullyQualifiedTypeNames")] 16 | public IObjectStringifier ObjectStringifier 17 | { 18 | init => UseFullyQualifiedTypeNames = ((ObjectStringifyImpl)value).UseFullyQualifiedTypeNames; 19 | } 20 | 21 | public bool AlwaysUseExplicitTypeArguments { get; init; } 22 | public bool UseFullyQualifiedTypeNames { get; init; } 23 | public bool AllowVerbatimStringLiterals { get; init; } = true; 24 | 25 | /// 26 | /// Omits builtin implicit casts (on by default). This can cause the code to select another overload or simply fail to 27 | /// compile, and the rules used to detect valid cast elision may be wrong in corner cases. 28 | /// If you're purely interested in accuracy over readability of the code, you may wish to turn this off. 29 | /// 30 | public bool OmitImplicitCasts { get; init; } = true; 31 | 32 | public int? PrintedListLengthLimit { get; init; } 33 | public int? MaximumValueLength { get; init; } 34 | 35 | /// 36 | /// The default formatter for converting an expression to code. 37 | /// 38 | public static readonly ExpressionToCodeConfiguration DefaultCodeGenConfiguration = new(); 39 | 40 | /// 41 | /// The default formatter for formatting an assertion violation. 42 | /// This is identical to DefaultCodeGenConfiguration, except that long enumerables or values with long string 43 | /// representations are truncated. 44 | /// 45 | public static readonly ExpressionToCodeConfiguration DefaultAssertionConfiguration = new() { 46 | PrintedListLengthLimit = 30, 47 | MaximumValueLength = 150, 48 | }; 49 | 50 | /// 51 | /// This configuration is used for PAssert.That(()=>...) and Expect(()=>...). Initially 52 | /// ExpressionToCodeConfiguration.DefaultAssertionConfiguration. 53 | /// 54 | /// This field is globally mutable to allow consumers to configure the library. If you wish to use multiple 55 | /// configurations, it is recommended 56 | /// to use the instance methods on a configuration instance instead of the static methods in PAssert, 57 | /// ExpressionToCode and ExpressionAssertions. 58 | /// 59 | /// 60 | // ReSharper disable once FieldCanBeMadeReadOnly.Global 61 | public static ExpressionToCodeConfiguration GlobalAssertionConfiguration = DefaultAssertionConfiguration; 62 | 63 | /// 64 | /// This configuration is used for Expression.ToCode(() => ...) and other code-generation methods. Initially 65 | /// ExpressionToCodeConfiguration.DefaultCodeGenConfiguration. 66 | /// 67 | /// This field is globally mutable to allow consumers to configure the library. If you wish to use multiple 68 | /// configurations, it is recommended 69 | /// to use the instance methods on a configuration instance instead of the static methods in PAssert, 70 | /// ExpressionToCode and ExpressionAssertions. 71 | /// 72 | /// 73 | // ReSharper disable once FieldCanBeMadeReadOnly.Global 74 | public static ExpressionToCodeConfiguration GlobalCodeGenConfiguration = DefaultCodeGenConfiguration; 75 | 76 | public IExpressionToCode GetExpressionToCode() 77 | => new ExpressionToCodeWrapper(this); 78 | 79 | public IAnnotatedToCode GetAnnotatedToCode() 80 | => new AnnotatedToCodeWrapper(this); 81 | 82 | public TypeToCodeConfig GetTypeToCode(bool includeGenericTypeArgumentNames) 83 | => new() { IncludeGenericTypeArgumentNames = includeGenericTypeArgumentNames, UseFullyQualifiedTypeNames = UseFullyQualifiedTypeNames, }; 84 | 85 | sealed record AnnotatedToCodeWrapper(ExpressionToCodeConfiguration config) : IAnnotatedToCode 86 | { 87 | public string AnnotatedToCode(Expression e, string? msg, bool outerValueIsAssertionFailure) 88 | => config.CodeAnnotator.AnnotateExpressionTree(config, e, msg, outerValueIsAssertionFailure); 89 | } 90 | 91 | sealed record ExpressionToCodeWrapper(ExpressionToCodeConfiguration config) : IExpressionToCode 92 | { 93 | public string ToCode(Expression e) 94 | => ExpressionToCodeString.ToCodeString(config, e); 95 | } 96 | 97 | [Obsolete] 98 | public ExpressionToCodeConfiguration WithCompiler(IExpressionCompiler v) 99 | => this with { ExpressionCompiler = v, }; 100 | 101 | [Obsolete] 102 | public ExpressionToCodeConfiguration WithAnnotator(ICodeAnnotator v) 103 | => this with { CodeAnnotator = v, }; 104 | 105 | [Obsolete] 106 | public ExpressionToCodeConfiguration WithPrintedListLengthLimit(int? v) 107 | => this with { PrintedListLengthLimit = v, }; 108 | 109 | [Obsolete] 110 | public ExpressionToCodeConfiguration WithObjectStringifier(IObjectStringifier withFullTypeNames) 111 | => this with { ObjectStringifier = withFullTypeNames, }; 112 | 113 | [Obsolete] 114 | public ExpressionToCodeConfiguration WithAlwaysUseExplicitTypeArguments(bool b) 115 | => this with { AlwaysUseExplicitTypeArguments = b, }; 116 | 117 | [Obsolete] 118 | public ExpressionToCodeConfiguration WithOmitImplicitCasts(bool b) 119 | => this with { OmitImplicitCasts = b, }; 120 | } 121 | 122 | public interface ICodeAnnotator 123 | { 124 | string AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string? msg, bool outerValueIsAssertionFailure); 125 | } 126 | 127 | public interface IExpressionCompiler 128 | { 129 | Func Compile(Expression> expression); 130 | Delegate Compile(LambdaExpression expression); 131 | } 132 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionToCodeLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 3.5.0 4 | Add support for throw expressions (thanks @girlpunk) 5 | 6 | Eamon Nerbonne 7 | Eamon Nerbonne 8 | ExpressionToCode 9 | Generates valid, readable C# from an expression tree, and can annotate that code with runtime values. Useful for e.g. code generation and unit testing assertions. 10 | false 11 | code-generation NUnit xUnit.NET mstest expression-tree unit-test PowerAssert ExpressionToCode 12 | Apache-2.0 13 | netstandard2.0;net8.0 14 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 15 | https://github.com/EamonNerbonne/ExpressionToCode 16 | https://github.com/EamonNerbonne/ExpressionToCode 17 | false 18 | true 19 | true 20 | latest 21 | enable 22 | NU1605 23 | CS1591 24 | 25 | 26 | 27 | 28 | portable 29 | True 30 | true 31 | true 32 | true 33 | snupkg 34 | 35 | 36 | 37 | 38 | 39 | True 40 | True 41 | ExpressionTypeDispatch.tt 42 | 43 | 44 | TextTemplatingFileGenerator 45 | ExpressionTypeDispatch.Generated.cs 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionTreeAssertion.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class ExpressionTreeAssertion 4 | { 5 | public static void Assert(this ExpressionToCodeConfiguration config, Expression> assertion, string? msg = null) 6 | { 7 | var compiled = config.ExpressionCompiler.Compile(assertion); 8 | bool ok; 9 | try { 10 | ok = compiled(); 11 | } catch (Exception e) { 12 | throw new InvalidOperationException(config.CodeAnnotator.AnnotateExpressionTree(config, assertion.Body, "evaluating assertion aborted due to exception: " + msg, true), e); 13 | } 14 | 15 | if (!ok) { 16 | throw new InvalidOperationException(config.CodeAnnotator.AnnotateExpressionTree(config, assertion.Body, msg ?? "assertion failed", true), null); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ExpressionTreeCompilers.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class ExpressionTreeCompilers 4 | { 5 | /// 6 | /// This compiler uses the .net built-in Expression.Compile() method to compile an expression tree. 7 | /// 8 | public static readonly IExpressionCompiler DotnetExpressionCompiler = new DotnetExpressionCompiler(); 9 | 10 | /// 11 | /// This expression tree compiler should have the same semantics as the .net built-in Expression.Compile() method, but 12 | /// it's faster. 13 | /// It only supports a subset of parameterless lambdas. 14 | /// Unsupported expressions fall-back to the builtin Expression.Compile methods. 15 | /// This compiler is relatively new, so if anything breaks, consider using the DotnetExpressionCompiler. 16 | /// 17 | public static readonly IExpressionCompiler FastExpressionCompiler = 18 | new FastExpressionCompilerImpl(); 19 | } 20 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/FormatStringParser.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class FormatStringParser 4 | { 5 | public struct Segment 6 | { 7 | public string InitialStringPart; 8 | public object? FollowedByValue; 9 | public string? WithFormatString; 10 | } 11 | 12 | public static (Segment[] segments, string Tail) ParseFormatString(string formatString, object[] formatArguments) 13 | => new FormattableStringParser(formatString, formatArguments).Finish(); 14 | 15 | sealed class FormattableStringParser : IFormatProvider, ICustomFormatter 16 | { 17 | //Format strings have exceptions for stuff like double curly braces 18 | //There are also corner-cases that non-compiler generated format strings might hit 19 | //All in all: rather than reimplement a custom parser, this 20 | readonly StringBuilder sb = new(); 21 | readonly List Segments = new(); 22 | 23 | public FormattableStringParser(string formatString, object[] formatArguments) 24 | => sb.AppendFormat(this, formatString, formatArguments); 25 | 26 | object IFormatProvider.GetFormat(Type? formatType) 27 | => this; 28 | 29 | public (Segment[] segments, string Tail) Finish() 30 | => (Segments.ToArray(), sb.ToString()); 31 | 32 | string ICustomFormatter.Format(string? format, object? arg, IFormatProvider? formatProvider) 33 | { 34 | Segments.Add( 35 | new() { 36 | InitialStringPart = sb.ToString(), 37 | FollowedByValue = arg, 38 | WithFormatString = format, 39 | }); 40 | sb.Length = 0; 41 | return ""; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System.Collections.Generic; 4 | global using System.Linq; 5 | global using System; 6 | global using System.Collections; 7 | global using System.Diagnostics.Contracts; 8 | global using System.Linq.Expressions; 9 | global using System.Reflection; 10 | global using System.Runtime.CompilerServices; 11 | global using System.Text; 12 | global using ExpressionToCodeLib.Internal; 13 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/IObjectStringifier.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | [Obsolete] 4 | public interface IObjectStringifier 5 | { 6 | [Obsolete] 7 | string? PlainObjectToCode(object? val, Type? type); 8 | 9 | [Obsolete] 10 | string TypeNameToCode(Type type); 11 | } 12 | 13 | [Obsolete] 14 | public static class ObjectStringifierExtensions 15 | { 16 | [Obsolete] 17 | public static string? PlainObjectToCode(this IObjectStringifier it, object? val) 18 | => it.PlainObjectToCode(val, val?.GetType()); 19 | } 20 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/DotnetExpressionCompiler.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | sealed class DotnetExpressionCompiler : IExpressionCompiler 4 | { 5 | public Func Compile(Expression> expression) 6 | => expression.Compile(true); 7 | 8 | public Delegate Compile(LambdaExpression expression) 9 | => expression.Compile(true); 10 | } 11 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ExpressionPrecedence.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | static class ExpressionPrecedence 4 | { 5 | static bool UnaryDashSym(ExpressionType et) 6 | => et == ExpressionType.Negate 7 | || et == ExpressionType.NegateChecked 8 | || et == ExpressionType.PreDecrementAssign; 9 | 10 | static bool UnaryPlusSym(ExpressionType et) 11 | => et == ExpressionType.UnaryPlus 12 | || et == ExpressionType.PreIncrementAssign; 13 | 14 | public static bool TokenizerConfusable(ExpressionType a, ExpressionType b) 15 | => UnaryDashSym(a) && UnaryDashSym(b) || UnaryPlusSym(a) && UnaryPlusSym(b); 16 | 17 | public static int Rank(ExpressionType exprType) 18 | => exprType switch { 19 | //brackets make no sense: 20 | ExpressionType.Block => -1, 21 | ExpressionType.Goto => -1, 22 | ExpressionType.Loop => -1, 23 | ExpressionType.Switch => -1, 24 | ExpressionType.Throw => -1, 25 | ExpressionType.Try => -1, 26 | ExpressionType.Label => -1, 27 | //brackets built-in; thus unnecesary (for params only!). 28 | ExpressionType.MemberInit => 1, 29 | ExpressionType.ArrayIndex => 1, 30 | ExpressionType.Call => 1, 31 | ExpressionType.Invoke => 1, 32 | ExpressionType.New => 1, 33 | ExpressionType.NewArrayInit => 1, 34 | ExpressionType.NewArrayBounds => 1, 35 | ExpressionType.ListInit => 1, 36 | ExpressionType.Power => 1, //non-native, uses Call. 37 | //other primary expressions 38 | ExpressionType.Constant => 1, 39 | ExpressionType.Parameter => 1, 40 | ExpressionType.MemberAccess => 1, 41 | ExpressionType.ArrayLength => 1, 42 | ExpressionType.Index => 1, 43 | ExpressionType.Default => 1, 44 | ExpressionType.PostIncrementAssign => 1, 45 | ExpressionType.PostDecrementAssign => 1, 46 | //unary prefixes 47 | ExpressionType.UnaryPlus => 2, 48 | ExpressionType.Negate => 2, 49 | ExpressionType.NegateChecked => 2, 50 | ExpressionType.Convert => 2, 51 | ExpressionType.ConvertChecked => 2, 52 | ExpressionType.Not => 2, //bitwise OR numeric! 53 | ExpressionType.OnesComplement => 2, //numeric 54 | ExpressionType.IsTrue => 2, //maybe? 55 | ExpressionType.IsFalse => 2, //maybe? 56 | ExpressionType.PreIncrementAssign => 2, 57 | ExpressionType.PreDecrementAssign => 2, 58 | //binary multiplicative 59 | ExpressionType.Modulo => 3, 60 | ExpressionType.Multiply => 3, 61 | ExpressionType.MultiplyChecked => 3, 62 | ExpressionType.Divide => 3, 63 | //binary addition 64 | ExpressionType.Add => 4, 65 | ExpressionType.AddChecked => 4, 66 | ExpressionType.Subtract => 4, 67 | ExpressionType.SubtractChecked => 4, 68 | ExpressionType.Decrement => 4, //nonnative; uses ... - 1 69 | ExpressionType.Increment => 4, //nonnative; uses ... - 1 70 | //binary shift 71 | ExpressionType.LeftShift => 5, 72 | ExpressionType.RightShift => 5, 73 | //relational excl. equals 74 | ExpressionType.LessThan => 6, 75 | ExpressionType.LessThanOrEqual => 6, 76 | ExpressionType.GreaterThan => 6, 77 | ExpressionType.GreaterThanOrEqual => 6, 78 | ExpressionType.TypeAs => 6, 79 | ExpressionType.TypeIs => 6, 80 | //equality 81 | ExpressionType.NotEqual => 7, 82 | ExpressionType.Equal => 7, 83 | //bitwise/eager 84 | ExpressionType.And => 8, 85 | ExpressionType.ExclusiveOr => 9, 86 | ExpressionType.Or => 10, 87 | //logical/shortcircuit: 88 | ExpressionType.AndAlso => 11, 89 | ExpressionType.OrElse => 12, 90 | //null-coalesce 91 | ExpressionType.Coalesce => 13, 92 | //ternary ? : 93 | ExpressionType.Conditional => 14, 94 | //assignments & lamba's 95 | ExpressionType.Lambda => 15, 96 | ExpressionType.Quote => 15, //maybe? 97 | ExpressionType.Assign => 15, 98 | ExpressionType.AddAssign => 15, 99 | ExpressionType.AndAssign => 15, 100 | ExpressionType.DivideAssign => 15, 101 | ExpressionType.ExclusiveOrAssign => 15, 102 | ExpressionType.LeftShiftAssign => 15, 103 | ExpressionType.ModuloAssign => 15, 104 | ExpressionType.MultiplyAssign => 15, 105 | ExpressionType.OrAssign => 15, 106 | ExpressionType.PowerAssign => 15, 107 | ExpressionType.RightShiftAssign => 15, 108 | ExpressionType.SubtractAssign => 15, 109 | ExpressionType.AddAssignChecked => 15, 110 | ExpressionType.MultiplyAssignChecked => 15, 111 | ExpressionType.SubtractAssignChecked => 15, 112 | _ => throw new ArgumentOutOfRangeException("Unsupported enum value:" + exprType), 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ExpressionToCodeString.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | sealed class ExpressionToCodeString 4 | { 5 | public static string ToCodeString(ExpressionToCodeConfiguration config, Expression e) 6 | { 7 | var sb = new StringBuilder(); 8 | var ignoreInitialSpace = true; 9 | var stringifiedExpr = new ExpressionToCodeImpl(config).ExpressionDispatch(e); 10 | AppendTo(sb, ref ignoreInitialSpace, stringifiedExpr); 11 | return sb.ToString(); 12 | } 13 | 14 | static void AppendTo(StringBuilder sb, ref bool ignoreInitialSpace, StringifiedExpression node) 15 | { 16 | if (node.Text != null) { 17 | _ = sb.Append(ignoreInitialSpace ? node.Text.TrimStart() : node.Text); 18 | ignoreInitialSpace = node.Text.Any() && ExpressionToCode.ShouldIgnoreSpaceAfter(node.Text[node.Text.Length - 1]); 19 | } else { 20 | foreach (var kid in node.Children) { 21 | AppendTo(sb, ref ignoreInitialSpace, kid); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ExpressionTypeDispatch.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" #> 2 | <#@ output extension=".Generated.cs" #> 3 | <#@ assembly name="System.Core" #> 4 | <#@ import namespace="System" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ import namespace="System.Linq.Expressions" #> 7 | <#@ import namespace="System.Linq" #> 8 | using System; 9 | using System.Linq.Expressions; 10 | using System.Diagnostics.Contracts; 11 | 12 | namespace ExpressionToCodeLib.Internal { 13 | internal interface IExpressionTypeDispatch { 14 | <# 15 | foreach(var v in ExpressionTypes()) { 16 | #> 17 | [Pure] T Dispatch<#=v.ToString()#>(Expression e); 18 | <# 19 | } 20 | #> 21 | } 22 | 23 | internal static class ExpressionTypeDispatcher { 24 | [Pure] public static T ExpressionDispatch(this IExpressionTypeDispatch dispatcher, Expression e) { 25 | try{ 26 | switch(e.NodeType) { 27 | <# 28 | foreach(var v in ExpressionTypes()) { 29 | #> 30 | case ExpressionType.<#=v.ToString()#>: return dispatcher.Dispatch<#=v.ToString()#>(e); 31 | <# 32 | } 33 | #> 34 | default: 35 | if(Enum.IsDefined(typeof(ExpressionType),e.NodeType)) 36 | throw new NotImplementedException("ExpressionToCode supports .NET 3.5 expressions only"); 37 | else 38 | throw new ArgumentOutOfRangeException("Impossible enum value:"+(int)e.NodeType); 39 | } 40 | } catch(NotImplementedException nie) { 41 | throw new ArgumentOutOfRangeException("Could not dispatch expr with nodetype "+e.NodeType+" and type " +e.GetType().Name,nie); 42 | } 43 | } 44 | } 45 | } 46 | 47 | <#+ 48 | IEnumerable ExpressionTypes() { 49 | return Enum.GetValues(typeof(ExpressionType)).Cast() 50 | //.Where(et=>et<= ExpressionType.TypeIs) //This check limits support to .NET 4.0 expressions 51 | ; 52 | } 53 | #> -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/FastExpressionCompilerImpl.cs: -------------------------------------------------------------------------------- 1 | using FastExpressionCompiler; 2 | 3 | namespace ExpressionToCodeLib.Internal; 4 | 5 | sealed class FastExpressionCompilerImpl : IExpressionCompiler 6 | { 7 | static readonly DotnetExpressionCompiler fallback = new(); 8 | 9 | public Func Compile(Expression> expression) 10 | => expression.TryCompile>() ?? fallback.Compile(expression); 11 | 12 | public Delegate Compile(LambdaExpression expression) 13 | => expression.TryCompile() ?? fallback.Compile(expression); 14 | } 15 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/NullabilityHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | static class NullabilityHelpers 4 | { 5 | static bool IsNullableValueType(this Type type) 6 | => type.GetTypeInfo().IsValueType && type.GetTypeInfo().IsGenericType 7 | && type.GetGenericTypeDefinition() == typeof(Nullable<>); 8 | 9 | public static Type EnsureNullability(this Type type) 10 | => !type.GetTypeInfo().IsValueType || type.IsNullableValueType() 11 | ? type 12 | : typeof(Nullable<>).MakeGenericType(type); 13 | 14 | public static Type AvoidNullability(this Type type) 15 | => !type.GetTypeInfo().IsValueType || !type.GetTypeInfo().IsGenericType 16 | || type.GetGenericTypeDefinition() != typeof(Nullable<>) 17 | ? type 18 | : type.GetTypeInfo().GetGenericArguments()[0]; 19 | } 20 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ObjectStringifyImpl.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | [Obsolete] 4 | sealed class ObjectStringifyImpl : IObjectStringifier 5 | { 6 | internal readonly bool UseFullyQualifiedTypeNames; 7 | 8 | public ObjectStringifyImpl(bool useFullyQualifiedTypeNames = false) 9 | => UseFullyQualifiedTypeNames = useFullyQualifiedTypeNames; 10 | 11 | [Obsolete] 12 | public string TypeNameToCode(Type type) 13 | => type.ToCSharpFriendlyTypeName(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration with { UseFullyQualifiedTypeNames = UseFullyQualifiedTypeNames, }, false); 14 | 15 | [Obsolete] 16 | public string? PlainObjectToCode(object? val, Type? type) 17 | => ObjectToCodeImpl.PlainObjectToCode(new() { UseFullyQualifiedTypeNames = UseFullyQualifiedTypeNames, }, val, type); 18 | } 19 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ReflectionHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | static class ReflectionHelpers 4 | { 5 | public static PropertyInfo? GetPropertyIfGetter(MethodInfo mi) 6 | { 7 | var supposedGetter = mi.Name.StartsWith("get_", StringComparison.Ordinal); 8 | 9 | if (!mi.IsSpecialName || !supposedGetter) { 10 | return null; 11 | } 12 | 13 | var pName = mi.Name.Substring(4); 14 | const BindingFlags bindingFlags = 15 | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; 16 | var pars = mi.GetParameters(); 17 | var declaringType = mi.DeclaringType; 18 | if (declaringType is null) { 19 | return null; 20 | } else if (pars.Length == 0) { 21 | return declaringType.GetTypeInfo().GetProperty(pName, bindingFlags); 22 | } else { 23 | // ReSharper disable once PossibleNullReferenceException 24 | foreach (var prop in declaringType.GetProperties(bindingFlags)) { 25 | if (prop.GetMethod == mi) { 26 | return prop; 27 | } 28 | } 29 | 30 | return null; 31 | } 32 | } 33 | 34 | public static bool IsMemberInfoStatic(MemberInfo mi) 35 | => mi switch { 36 | FieldInfo fieldInfo => fieldInfo.IsStatic, 37 | MethodInfo methodInfo => (methodInfo.Attributes & MethodAttributes.Static) == MethodAttributes.Static, 38 | PropertyInfo pi => (pi.GetGetMethod(true) ?? pi.GetSetMethod(true))?.IsStatic ?? throw new("a property must have either a getter or setter"), 39 | EventInfo eventInfo => eventInfo.GetAddMethod(true)?.IsStatic ?? throw new("An event must have a backing add method"), 40 | { MemberType: MemberTypes.NestedType, } => true, 41 | _ => throw new ArgumentOutOfRangeException(nameof(mi), "Expression represents a member access for member" + mi.Name + " of member-type " + mi.MemberType + " that is unsupported"), 42 | }; 43 | 44 | public static bool HasBuiltinConversion(Type from, Type to) 45 | => from == typeof(sbyte) 46 | && (to == typeof(short) || to == typeof(int) || to == typeof(long) || to == typeof(float) 47 | || to == typeof(double) || to == typeof(decimal)) 48 | || from == typeof(byte) 49 | && (to == typeof(short) || to == typeof(ushort) || to == typeof(int) || to == typeof(uint) 50 | || to == typeof(long) || to == typeof(ulong) || to == typeof(float) || to == typeof(double) 51 | || to == typeof(decimal)) 52 | || from == typeof(short) 53 | && (to == typeof(int) || to == typeof(long) || to == typeof(float) || to == typeof(double) 54 | || to == typeof(decimal)) 55 | || from == typeof(ushort) 56 | && (to == typeof(int) || to == typeof(uint) || to == typeof(long) || to == typeof(ulong) 57 | || to == typeof(float) || to == typeof(double) || to == typeof(decimal)) 58 | || from == typeof(int) 59 | && (to == typeof(long) || to == typeof(float) || to == typeof(double) || to == typeof(decimal)) 60 | || from == typeof(uint) 61 | && (to == typeof(long) || to == typeof(ulong) || to == typeof(float) || to == typeof(double) 62 | || to == typeof(decimal)) 63 | || from == typeof(long) && (to == typeof(float) || to == typeof(double) || to == typeof(decimal)) 64 | || from == typeof(char) 65 | && (to == typeof(ushort) || to == typeof(int) || to == typeof(uint) || to == typeof(long) 66 | || to == typeof(ulong) || to == typeof(float) || to == typeof(double) || to == typeof(decimal)) 67 | || from == typeof(float) && to == typeof(double) 68 | || from == typeof(ulong) && (to == typeof(float) || to == typeof(double) || to == typeof(decimal)); 69 | 70 | public static bool CanImplicitlyCast(Type from, Type to) 71 | => to.GetTypeInfo().IsAssignableFrom(from) || HasBuiltinConversion(from, to); 72 | 73 | public enum TypeClass 74 | { 75 | BuiltinType, 76 | AnonymousType, 77 | ClosureType, 78 | StructType, 79 | NormalType, 80 | TopLevelProgramClosureType, 81 | } 82 | 83 | public static TypeClass GuessTypeClass(this Type type) 84 | { 85 | var typeInfo = type.GetTypeInfo(); 86 | if (typeInfo.IsArray) { 87 | return TypeClass.BuiltinType; 88 | } 89 | 90 | var compilerGenerated = typeInfo.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any(); 91 | var name = type.Name; 92 | var named_DisplayClass = name.Contains("_DisplayClass"); 93 | var name_StartWithLessThan = name.StartsWith("<", StringComparison.Ordinal); 94 | var isBuiltin = typeInfo.IsPrimitive || typeInfo.IsEnum || type == typeof(decimal) || type == typeof(string) 95 | || typeof(Type).GetTypeInfo().IsAssignableFrom(type); 96 | 97 | if (name_StartWithLessThan && compilerGenerated) { 98 | var named_AnonymousType = name.Contains("AnonymousType"); 99 | var isGeneric = typeInfo.IsGenericType; 100 | var isNested = type.IsNested; 101 | 102 | if (!isBuiltin && isGeneric && !isNested && named_AnonymousType) { 103 | return TypeClass.AnonymousType; 104 | } else if (!isBuiltin && isNested && named_DisplayClass) { 105 | return TypeClass.ClosureType; 106 | } 107 | //note that since genericness+nestedness don't overlap, these typeclasses aren't confusable. 108 | else { 109 | throw new ArgumentException( 110 | "Can't deal with unknown-style compiler generated class " + type.FullName + " " + named_AnonymousType + ", " + named_DisplayClass + ", " + isGeneric 111 | + ", " + isNested); 112 | } 113 | } else if (!compilerGenerated && !name_StartWithLessThan) { 114 | return isBuiltin ? TypeClass.BuiltinType : typeInfo.IsValueType ? TypeClass.StructType : TypeClass.NormalType; 115 | } else if (!compilerGenerated && name_StartWithLessThan && named_DisplayClass && type.Namespace is null && type.IsNested && type.DeclaringType == type.Assembly.EntryPoint?.DeclaringType) { 116 | return TypeClass.TopLevelProgramClosureType; 117 | } else { 118 | throw new ArgumentException("Unusual type, heuristics uncertain:" + name); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/StringifiedExpression.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | readonly struct StringifiedExpression 4 | { 5 | //a node cannot have children and text. If it has neither, it is considered empty. 6 | public readonly string? Text; 7 | readonly StringifiedExpression[]? children; 8 | 9 | //can only have a value it it has text. 10 | public readonly Expression? OptionalValue; 11 | 12 | /// 13 | /// The expression tree contains many symbols that are not themselves "real" expressions, e.g. the "." in "obj.field". 14 | /// This field is true for parts that aren't just implementation details, but proper sub-expressions; e.g. the "x" in 15 | /// "x && y" 16 | /// 17 | public readonly bool IsConceptualChild; 18 | 19 | static readonly StringifiedExpression[] empty = { }; 20 | public StringifiedExpression[] Children => children ?? empty; 21 | 22 | StringifiedExpression(string? text, StringifiedExpression[]? children, Expression? optionalValue, bool isConceptualChild) 23 | { 24 | Text = text; 25 | this.children = children; 26 | OptionalValue = optionalValue; 27 | IsConceptualChild = isConceptualChild; 28 | } 29 | 30 | [Pure] 31 | public static StringifiedExpression TextOnly(string? text) 32 | => new(text, null, null, false); 33 | 34 | [Pure] 35 | public static StringifiedExpression TextAndExpr(string text, Expression expr) 36 | { 37 | if (expr == null) { 38 | throw new ArgumentNullException(nameof(expr)); 39 | } 40 | 41 | return new(text, null, expr, false); 42 | } 43 | 44 | [Pure] 45 | public static StringifiedExpression WithChildren(StringifiedExpression[] children) 46 | => new(null, children, null, false); 47 | 48 | [Pure] 49 | public override string ToString() 50 | => Text ?? string.Join("", Children); 51 | 52 | public StringifiedExpression MarkAsConceptualChild() 53 | => new(Text, children, OptionalValue, true); 54 | } 55 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/SubExpressionPerLineCodeAnnotator.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | class SubExpressionPerLineCodeAnnotator : ICodeAnnotator 4 | { 5 | public string AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string? msg, bool outerValueIsAssertionFailure) 6 | => (msg == null ? "" : msg + "\n\n") + ExpressionWithSubExpressions.Create(config, expr, outerValueIsAssertionFailure).ComposeToSingleString(); 7 | 8 | struct ExpressionWithSubExpressions 9 | { 10 | const string spacedArrow = " → "; 11 | string ExpressionString; 12 | SubExpressionValue[] SubExpressions; 13 | 14 | public struct SubExpressionValue : IEquatable 15 | { 16 | public string SubExpression, 17 | ValueAsString; 18 | 19 | public override int GetHashCode() 20 | => SubExpression.GetHashCode() + 37 * ValueAsString.GetHashCode(); 21 | 22 | public override bool Equals(object? obj) 23 | => obj is SubExpressionValue val && Equals(val); 24 | 25 | public bool Equals(SubExpressionValue val) 26 | => SubExpression == val.SubExpression && ValueAsString == val.ValueAsString; 27 | } 28 | 29 | public static ExpressionWithSubExpressions Create(ExpressionToCodeConfiguration config, Expression e, bool outerValueIsAssertionFailure) 30 | { 31 | var sb = new StringBuilder(); 32 | var ignoreInitialSpace = true; 33 | var node = new ExpressionToCodeImpl(config).ExpressionDispatch(e); 34 | AppendNodeToStringBuilder(sb, node, ref ignoreInitialSpace); 35 | var fullExprText = sb.ToString(); 36 | var subExpressionValues = new List(); 37 | FindSubExpressionValues(config, node, node, subExpressionValues, outerValueIsAssertionFailure); 38 | var assertionValue = outerValueIsAssertionFailure ? OutermostValue(config, node) : null; 39 | return new() { 40 | ExpressionString = fullExprText 41 | + (assertionValue != null ? "\n" + spacedArrow + assertionValue + " (caused assertion failure)\n" : ""), 42 | SubExpressions = subExpressionValues.Distinct().ToArray(), 43 | }; 44 | } 45 | 46 | static string? OutermostValue(ExpressionToCodeConfiguration config, StringifiedExpression node) 47 | { 48 | if (node.OptionalValue != null) { 49 | return ObjectToCodeImpl.ExpressionValueAsCode(config, node.OptionalValue, 10); 50 | } 51 | 52 | foreach (var kid in node.Children) { 53 | if (!kid.IsConceptualChild) { 54 | var value = OutermostValue(config, kid); 55 | if (value != null) { 56 | return value; 57 | } 58 | } 59 | } 60 | 61 | foreach (var kid in node.Children) { 62 | if (kid.IsConceptualChild) { 63 | var value = OutermostValue(config, kid); 64 | if (value != null) { 65 | return value; 66 | } 67 | } 68 | } 69 | 70 | return null; 71 | } 72 | 73 | static void AppendNodeToStringBuilder(StringBuilder sb, StringifiedExpression node, ref bool ignoreInitialSpace) 74 | { 75 | if (node.Text != null) { 76 | var trimmedText = ignoreInitialSpace ? node.Text.TrimStart() : node.Text; 77 | _ = sb.Append(trimmedText); 78 | ignoreInitialSpace = node.Text != "" && ExpressionToCode.ShouldIgnoreSpaceAfter(node.Text[node.Text.Length - 1]); 79 | } else { 80 | foreach (var kid in node.Children) { 81 | AppendNodeToStringBuilder(sb, kid, ref ignoreInitialSpace); 82 | } 83 | } 84 | } 85 | 86 | static void FindSubExpressionValues( 87 | ExpressionToCodeConfiguration config, 88 | StringifiedExpression node, 89 | StringifiedExpression subExprNode, 90 | List subExpressionValues, 91 | bool outerValueIsAssertionFailure) 92 | { 93 | if (!outerValueIsAssertionFailure && node.OptionalValue != null) { 94 | var sb = new StringBuilder(); 95 | var ignoreInitialSpace = true; 96 | var valueString = ObjectToCodeImpl.ExpressionValueAsCode(config, node.OptionalValue, 10); 97 | AppendNodeToStringBuilder(sb, subExprNode, ref ignoreInitialSpace); 98 | var maxSize = Math.Max(40, config.MaximumValueLength ?? 200); 99 | var subExprString = sb.Length <= maxSize 100 | ? sb.ToString() 101 | : sb.ToString(0, maxSize / 2 - 1) + " … " + sb.ToString(sb.Length - (maxSize / 2 - 1), maxSize / 2 - 1); 102 | // ReSharper disable once ReplaceWithStringIsNullOrEmpty - for nullability analysis 103 | if (valueString != null && valueString != "") { 104 | subExpressionValues.Add(new() { SubExpression = subExprString, ValueAsString = valueString, }); 105 | } 106 | } 107 | 108 | foreach (var kid in node.Children) { 109 | if (!kid.IsConceptualChild) { 110 | FindSubExpressionValues(config, kid, subExprNode, subExpressionValues, outerValueIsAssertionFailure); 111 | } 112 | } 113 | 114 | foreach (var kid in node.Children) { 115 | if (kid.IsConceptualChild) { 116 | FindSubExpressionValues(config, kid, kid, subExpressionValues, false); 117 | } 118 | } 119 | } 120 | 121 | public string ComposeToSingleString() 122 | { 123 | var maxLineLength = SubExpressions.Max(sub => sub.SubExpression.Length + spacedArrow.Length + sub.ValueAsString.Length as int?) ?? 0; 124 | var maxExprLength = SubExpressions.Max(sub => sub.SubExpression.Length as int?) ?? 0; 125 | var containsANewline = SubExpressions.Any(sub => sub.SubExpression.Contains("\n") || sub.ValueAsString.Contains("\n")); 126 | 127 | return ExpressionString + "\n" 128 | + string.Join( 129 | "", 130 | maxLineLength <= 80 && maxExprLength <= 30 && !containsANewline 131 | ? SubExpressions.Select(sub => sub.SubExpression.PadLeft(maxExprLength) + spacedArrow + sub.ValueAsString + "\n") 132 | : SubExpressions.Select(sub => sub.SubExpression + "\n " + spacedArrow + sub.ValueAsString + "\n\n") 133 | ); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/TypeToCodeConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | public readonly record struct TypeToCodeConfig 4 | { 5 | public bool UseFullyQualifiedTypeNames { get; init; } 6 | public bool IncludeGenericTypeArgumentNames { get; init; } 7 | 8 | public string GetTypeName(Type type) 9 | => AliasNameOrNull(type) ?? NullableTypeNameOrNull(type.GetTypeInfo()) ?? ArrayTypeNameOrNull(type) ?? ValueTupleTypeNameOrNull(type) ?? GetUnaliasedTypeName(type); 10 | 11 | string? ValueTupleTypeNameOrNull(Type type) 12 | { 13 | if (!IsValueTupleType(type.GetTypeInfo())) { 14 | return null; 15 | } 16 | 17 | var output = new StringBuilder(); 18 | _ = output.Append("("); 19 | var genericArguments = type.GetTypeInfo().GetGenericArguments(); 20 | var nextIdx = 0; 21 | while (nextIdx < genericArguments.Length) { 22 | var typePar = genericArguments[nextIdx]; 23 | if (nextIdx + 1 == genericArguments.Length) { 24 | if (nextIdx == 7 && IsValueTupleType(typePar.GetTypeInfo())) { 25 | genericArguments = typePar.GetTypeInfo().GetGenericArguments(); 26 | nextIdx = 0; 27 | } else { 28 | _ = output.Append(GetTypeName(typePar)); 29 | break; 30 | } 31 | } else { 32 | _ = output.Append(GetTypeName(typePar)); 33 | _ = output.Append(", "); 34 | nextIdx++; 35 | } 36 | } 37 | 38 | _ = output.Append(")"); 39 | return output.ToString(); 40 | } 41 | 42 | internal static bool IsValueTupleType(TypeInfo typeInfo) 43 | => typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition && typeInfo.Namespace == "System" && typeInfo.Name.StartsWith("ValueTuple`", StringComparison.Ordinal); 44 | 45 | string GetUnaliasedTypeName(Type type) 46 | { 47 | var typeNameWithoutNamespace = 48 | GenericTypeName(type) 49 | ?? NormalName(type); 50 | return UseFullyQualifiedTypeNames ? type.Namespace + "." + typeNameWithoutNamespace : typeNameWithoutNamespace; 51 | } 52 | 53 | static string? AliasNameOrNull(Type type) 54 | => type switch { 55 | _ when type == typeof(bool) => "bool", 56 | _ when type == typeof(byte) => "byte", 57 | _ when type == typeof(sbyte) => "sbyte", 58 | _ when type == typeof(char) => "char", 59 | _ when type == typeof(decimal) => "decimal", 60 | _ when type == typeof(double) => "double", 61 | _ when type == typeof(float) => "float", 62 | _ when type == typeof(int) => "int", 63 | _ when type == typeof(uint) => "uint", 64 | _ when type == typeof(long) => "long", 65 | _ when type == typeof(ulong) => "ulong", 66 | _ when type == typeof(object) => "object", 67 | _ when type == typeof(short) => "short", 68 | _ when type == typeof(ushort) => "ushort", 69 | _ when type == typeof(string) => "string", 70 | _ when type == typeof(void) => "void", 71 | { IsGenericParameter: true, } => type.Name, 72 | _ => null, 73 | }; 74 | 75 | string? NullableTypeNameOrNull(TypeInfo typeInfo) 76 | => typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) ? GetTypeName(typeInfo.GetGenericArguments().Single()) + "?" : null; 77 | 78 | string NormalName(Type type) 79 | { 80 | if (type.DeclaringType != null) { 81 | return (this with { UseFullyQualifiedTypeNames = false, }).GetTypeName(type.DeclaringType) + "." + type.Name; 82 | } else { 83 | return type.Name; 84 | } 85 | } 86 | 87 | string? GenericTypeName(Type? type) 88 | { 89 | if (type == null || !type.GetTypeInfo().IsGenericType) { 90 | return null; 91 | } 92 | 93 | var renderAsGenericTypeDefinition = !IncludeGenericTypeArgumentNames && type.GetTypeInfo().IsGenericTypeDefinition; 94 | 95 | var typeArgs = type.GetTypeInfo().GetGenericArguments(); 96 | var typeArgIdx = typeArgs.Length; 97 | var revNestedTypeNames = new List(); 98 | 99 | while (type != null) { 100 | var name = type.Name; 101 | var backtickIdx = name.IndexOf('`'); 102 | if (backtickIdx == -1) { 103 | revNestedTypeNames.Add(name); 104 | } else { 105 | var afterArgCountIdx = name.IndexOf('[', backtickIdx + 1); 106 | if (afterArgCountIdx == -1) { 107 | afterArgCountIdx = name.Length; 108 | } 109 | 110 | var thisTypeArgCount = int.Parse(name.Substring(backtickIdx + 1, afterArgCountIdx - backtickIdx - 1)); 111 | if (renderAsGenericTypeDefinition) { 112 | typeArgIdx -= thisTypeArgCount; 113 | revNestedTypeNames.Add(name.Substring(0, backtickIdx) + "<" + new string(',', thisTypeArgCount - 1) + ">"); 114 | } else { 115 | var argNames = new List(); 116 | for (var i = typeArgIdx - thisTypeArgCount; i < typeArgIdx; i++) { 117 | argNames.Add(GetTypeName(typeArgs[i])); 118 | } 119 | 120 | typeArgIdx -= thisTypeArgCount; 121 | revNestedTypeNames.Add(name.Substring(0, backtickIdx) + "<" + string.Join(", ", argNames) + ">"); 122 | } 123 | } 124 | 125 | type = type.DeclaringType; 126 | } 127 | 128 | revNestedTypeNames.Reverse(); 129 | return string.Join(".", revNestedTypeNames); 130 | } 131 | 132 | string? ArrayTypeNameOrNull(Type type) 133 | { 134 | if (!type.IsArray) { 135 | return null; 136 | } 137 | 138 | var arraySuffix = default(string?); 139 | do { 140 | var rankCommas = new string(',', type.GetArrayRank() - 1); 141 | type = type.GetElementType() ?? throw new("Arrays must have an element type"); 142 | arraySuffix = arraySuffix + "[" + rankCommas + "]"; 143 | // ReSharper disable once PossibleNullReferenceException 144 | } while (type.IsArray); 145 | 146 | var basename = GetTypeName(type); 147 | return basename + arraySuffix; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/Internal/ValuesOnStalksCodeAnnotator.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib.Internal; 2 | 3 | sealed class ValuesOnStalksCodeAnnotator : ICodeAnnotator 4 | { 5 | public string AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string? msg, bool outerValueIsAssertionFailure) 6 | { 7 | var splitLine = ExpressionToStringWithValues(config, expr, outerValueIsAssertionFailure); 8 | 9 | var exprWithStalkedValues = new StringBuilder(); 10 | if (msg == null) { 11 | _ = exprWithStalkedValues.AppendLine(splitLine.Line); 12 | } else if (IsMultiline(msg)) { 13 | _ = exprWithStalkedValues.AppendLine(msg); 14 | _ = exprWithStalkedValues.AppendLine(splitLine.Line); 15 | } else { 16 | _ = exprWithStalkedValues.AppendLine(splitLine.Line + " : " + msg); 17 | } 18 | 19 | for (var nodeI = splitLine.Nodes.Length - 1; nodeI >= 0; nodeI--) { 20 | var stalkLine = new string('\u2007', splitLine.Nodes[nodeI].Location).ToCharArray(); //figure-spaces. 21 | for (var i = 0; i < stalkLine.Length; i++) { 22 | if (splitLine.Line[i] == ' ') { 23 | stalkLine[i] = ' '; //use normal spaces where the expr used normal spaces for more natural spacing. 24 | } 25 | } 26 | 27 | for (var prevI = 0; prevI < nodeI; prevI++) { 28 | stalkLine[splitLine.Nodes[prevI].Location] = '\u2502'; //light vertical lines 29 | } 30 | 31 | _ = exprWithStalkedValues.AppendLine((new string(stalkLine) + splitLine.Nodes[nodeI].Value).TrimEnd()); 32 | } 33 | 34 | return exprWithStalkedValues.ToString(); 35 | } 36 | 37 | static bool IsMultiline(string msg) 38 | { 39 | var idxAfterNewline = msg.IndexOf('\n') + 1; 40 | return idxAfterNewline > 0 && idxAfterNewline < msg.Length; 41 | } 42 | 43 | static SplitExpressionLine ExpressionToStringWithValues(ExpressionToCodeConfiguration config, Expression e, bool outerValueIsAssertionFailure) 44 | { 45 | var nodeInfos = new List(); 46 | var sb = new StringBuilder(); 47 | var ignoreInitialSpace = true; 48 | var node = new ExpressionToCodeImpl(config).ExpressionDispatch(e); 49 | AppendTo(config, sb, nodeInfos, node, ref ignoreInitialSpace, !outerValueIsAssertionFailure); 50 | nodeInfos.Add(new() { Location = sb.Length, Value = null, }); 51 | return new() { Line = sb.ToString().TrimEnd(), Nodes = nodeInfos.ToArray(), }; 52 | } 53 | 54 | static void AppendTo( 55 | ExpressionToCodeConfiguration config, 56 | StringBuilder sb, 57 | List nodeInfos, 58 | StringifiedExpression node, 59 | ref bool ignoreInitialSpace, 60 | bool showTopExpressionValue) 61 | { 62 | if (node.Text != null) { 63 | var trimmedText = ignoreInitialSpace ? node.Text.TrimStart() : node.Text; 64 | var pos0 = sb.Length; 65 | _ = sb.Append(trimmedText); 66 | ignoreInitialSpace = node.Text.Any() && ExpressionToCode.ShouldIgnoreSpaceAfter(node.Text[node.Text.Length - 1]); 67 | if (showTopExpressionValue) { 68 | var valueString = node.OptionalValue == null ? null : ObjectToCodeImpl.ExpressionValueAsCode(config, node.OptionalValue, 0); 69 | if (valueString != null) { 70 | nodeInfos.Add(new() { Location = pos0 + trimmedText.Length / 2, Value = valueString, }); 71 | } 72 | } 73 | } 74 | 75 | foreach (var kid in node.Children) { 76 | AppendTo(config, sb, nodeInfos, kid, ref ignoreInitialSpace, showTopExpressionValue || kid.IsConceptualChild); 77 | } 78 | } 79 | 80 | struct SplitExpressionLine 81 | { 82 | public string Line; 83 | public SubExpressionInfo[] Nodes; 84 | } 85 | 86 | struct SubExpressionInfo 87 | { 88 | public int Location; 89 | public string? Value; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ObjectStringify.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class ObjectStringify 4 | { 5 | public static readonly IObjectStringifier Default = new ObjectStringifyImpl(); 6 | public static readonly IObjectStringifier WithFullTypeNames = new ObjectStringifyImpl(true); 7 | } 8 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/ObjectToCode.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | /// 4 | /// If you wish to override some formatting aspects of these methods, set 5 | /// ExpressionToCodeConfiguration.GlobalCodeGetConfiguration. 6 | /// 7 | public static class ObjectToCode 8 | { 9 | public static string ComplexObjectToPseudoCode(object? val) 10 | => ObjectToCodeImpl.ComplexObjectToPseudoCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, val, 0); 11 | 12 | public static string ComplexObjectToPseudoCode(this ExpressionToCodeConfiguration config, object? val) 13 | => ObjectToCodeImpl.ComplexObjectToPseudoCode(config, val, 0); 14 | 15 | public static string? PlainObjectToCode(object? val) 16 | => ObjectToCodeImpl.PlainObjectToCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, val, val?.GetType()); 17 | 18 | public static string? PlainObjectToCode(object? val, Type? type) 19 | => ObjectToCodeImpl.PlainObjectToCode(ExpressionToCodeConfiguration.GlobalCodeGenConfiguration, val, type); 20 | 21 | public static string ToCSharpFriendlyTypeName(this Type type) 22 | => ExpressionToCodeConfiguration.GlobalCodeGenConfiguration.GetTypeToCode(true).GetTypeName(type); 23 | 24 | public static string ToCSharpFriendlyTypeName(this Type type, ExpressionToCodeConfiguration config, bool includeGenericTypeArgumentNames) 25 | => config.GetTypeToCode(includeGenericTypeArgumentNames).GetTypeName(type); 26 | 27 | public static string ToCSharpFriendlyTypeName(this Type type, bool useFullyQualifiedTypeNames, bool includeGenericTypeArgumentNames) 28 | => new TypeToCodeConfig { IncludeGenericTypeArgumentNames = includeGenericTypeArgumentNames, UseFullyQualifiedTypeNames = useFullyQualifiedTypeNames, }.GetTypeName(type); 29 | } 30 | -------------------------------------------------------------------------------- /ExpressionToCodeLib/PAssert.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeLib; 2 | 3 | public static class PAssert 4 | { 5 | [Obsolete("Prefer PAssert.That: IsTrue is provided for compatibility with PowerAssert.NET")] 6 | public static void IsTrue(Expression> assertion) 7 | => That(assertion); 8 | 9 | /// 10 | /// Evaluates an assertion and throws an exception the assertion it returns false or throws an exception. 11 | /// The exception includes the code of the assertion annotated with runtime values for its sub-expressions. 12 | /// Identical functionality is available via Expect(()=>...); this can be accessed via "using static 13 | /// ExpressionToCodeLib.ExpressionExpectations;". 14 | /// If you want to change the layout of the value annotations, see 15 | /// ExpressionToCodeConfiguration.GlobalAssertionConfiguration 16 | /// 17 | public static void That(Expression> assertion, string? msg = null) 18 | => ExpressionToCodeConfiguration.GlobalAssertionConfiguration.Assert(assertion, msg); 19 | } 20 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/AnnotatedToCodeTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class AnnotatedToCodeTest 4 | { 5 | [Fact] 6 | public void A1PlusB2() 7 | { 8 | var a = 1; 9 | var b = a + 1; 10 | 11 | var code = ExpressionToCode.AnnotatedToCode(() => a + b); 12 | 13 | Assert.Contains("a", code); 14 | Assert.Contains("+", code); 15 | Assert.Contains("b", code); 16 | Assert.Contains("1", code); 17 | Assert.Contains("2", code); 18 | Assert.Contains("3", code); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/AnonymousObjectFormattingTest.AnonymousObjectsInArrayExpression.approved.txt: -------------------------------------------------------------------------------- 1 | () => arr.Any() 2 |        │   │ 3 |        │   true 4 |        new[] { 5 | new { 6 | Name = "hmm", 7 | Val = 3, 8 | }, 9 | new { 10 | Name = "foo", 11 | Val = "test", 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/AnonymousObjectFormattingTest.MessyEnumerablesOfAnonymousObjects.approved.txt: -------------------------------------------------------------------------------- 1 | new[] { 2 | new { 3 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##3 ... 4 | A_short_string = "short", 5 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... ... 6 | }, 7 | new { 8 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##3 ... 9 | A_short_string = "short", 10 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... ... 11 | }, 12 | } -------------------------------------------------------------------------------- /ExpressionToCodeTest/AnonymousObjectFormattingTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class AnonymousObjectFormattingTest 4 | { 5 | [Fact] 6 | public void AnonymousObjectsRenderAsCode() 7 | => Assert.Equal( 8 | @"new { 9 | A = 1, 10 | Foo = ""Bar"", 11 | }", 12 | ObjectToCode.ComplexObjectToPseudoCode(new { A = 1, Foo = "Bar", })); 13 | 14 | [Fact] 15 | public void AnonymousObjectsInArray() 16 | => Assert.Equal( 17 | @"new[] { 18 | new { 19 | Val = 3, 20 | }, 21 | new { 22 | Val = 42, 23 | }, 24 | }", 25 | ObjectToCode.ComplexObjectToPseudoCode(new[] { new { Val = 3, }, new { Val = 42 } })); 26 | 27 | [Fact] 28 | public void AnonymousObjectsInArrayExpression() 29 | { 30 | var arr = new[] { new { Name = "hmm", Val = (object)3, }, new { Name = "foo", Val = (object)"test" } }; 31 | var config = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithAnnotator(CodeAnnotators.ValuesOnStalksCodeAnnotator); 32 | ApprovalTest.Verify(config.AnnotatedToCode(() => arr.Any())); 33 | } 34 | 35 | [Fact] 36 | public void MessyEnumerablesOfAnonymousObjects() 37 | { 38 | var foo = new { 39 | A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", 40 | A_short_string = "short", 41 | A_long_enumerable = Enumerable.Range(0, 1000) 42 | }; 43 | ApprovalTest.Verify( 44 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.ComplexObjectToPseudoCode( 45 | new[] { 46 | foo, 47 | foo, 48 | })); 49 | } 50 | 51 | [Fact] 52 | public void EnumerableInAnonymousObject() 53 | => Assert.Equal( 54 | @"new { 55 | Nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, ... }, 56 | }", 57 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.ComplexObjectToPseudoCode(new { Nums = Enumerable.Range(1, 100) })); 58 | 59 | [Fact] 60 | public void EnumInAnonymousObject() 61 | => Assert.Equal( 62 | @"new { 63 | Enum = ConsoleKey.A, 64 | }", 65 | ObjectToCode.ComplexObjectToPseudoCode(new { Enum = ConsoleKey.A })); 66 | } 67 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ApiStabilityTest.PublicApi.approved.txt: -------------------------------------------------------------------------------- 1 | enum EqualityExpressionClass 2 | int value__ 3 | const EqualityExpressionClass None = 0 4 | const EqualityExpressionClass EqualsOp = 1 5 | const EqualityExpressionClass NotEqualsOp = 2 6 | const EqualityExpressionClass ObjectEquals = 3 7 | const EqualityExpressionClass ObjectEqualsStatic = 4 8 | const EqualityExpressionClass ObjectReferenceEquals = 5 9 | const EqualityExpressionClass EquatableEquals = 6 10 | const EqualityExpressionClass SequenceEqual = 7 11 | const EqualityExpressionClass StructuralEquals = 8 12 | 13 | interface IAnnotatedToCode 14 | string inst.AnnotatedToCode(Expression e, string msg, bool outerValueIsAssertionFailure) 15 | 16 | interface ICodeAnnotator 17 | string inst.AnnotateExpressionTree(ExpressionToCodeConfiguration config, Expression expr, string msg, bool outerValueIsAssertionFailure) 18 | 19 | interface IExpressionCompiler 20 | Func inst.Compile(Expression> expression) 21 | Delegate inst.Compile(LambdaExpression expression) 22 | 23 | interface IExpressionToCode 24 | string inst.ToCode(Expression e) 25 | 26 | interface IObjectStringifier 27 | string inst.PlainObjectToCode(object val, Type type) 28 | string inst.TypeNameToCode(Type type) 29 | bool inst.PreferLiteralSyntax(string str1) 30 | 31 | class AnnotatedToCodeExtensions 32 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression e) 33 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e) 34 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e) 35 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e) 36 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e) 37 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e, string msg, bool outerValueIsAssertionFailure) 38 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e, string msg, bool outerValueIsAssertionFailure) 39 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e, string msg, bool outerValueIsAssertionFailure) 40 | string TYPE.AnnotatedToCode(IAnnotatedToCode it, Expression> e, string msg, bool outerValueIsAssertionFailure) 41 | 42 | class CodeAnnotators 43 | static readonly ICodeAnnotator ValuesOnStalksCodeAnnotator 44 | static readonly ICodeAnnotator SubExpressionPerLineCodeAnnotator 45 | 46 | class ExpressionExpectations 47 | void TYPE.Expect(Expression> assertion, string msg) 48 | 49 | class ExpressionToCode 50 | string TYPE.ToCode(Expression> e) 51 | string TYPE.ToCode(Expression> e) 52 | string TYPE.ToCode(Expression> e) 53 | string TYPE.ToCode(Expression> e) 54 | string TYPE.ToCode(Expression e) 55 | string TYPE.AnnotatedToCode(Expression> e) 56 | string TYPE.AnnotatedToCode(Expression> e) 57 | string TYPE.AnnotatedToCode(Expression> e) 58 | string TYPE.AnnotatedToCode(Expression> e) 59 | string TYPE.AnnotatedToCode(Expression expr) 60 | string TYPE.ToCode(ExpressionToCodeConfiguration config, Expression> e) 61 | string TYPE.ToCode(ExpressionToCodeConfiguration config, Expression> e) 62 | string TYPE.ToCode(ExpressionToCodeConfiguration config, Expression> e) 63 | string TYPE.ToCode(ExpressionToCodeConfiguration config, Expression> e) 64 | string TYPE.ToCode(ExpressionToCodeConfiguration config, Expression e) 65 | string TYPE.AnnotatedToCode(ExpressionToCodeConfiguration config, Expression> e) 66 | string TYPE.AnnotatedToCode(ExpressionToCodeConfiguration config, Expression> e) 67 | string TYPE.AnnotatedToCode(ExpressionToCodeConfiguration config, Expression> e) 68 | string TYPE.AnnotatedToCode(ExpressionToCodeConfiguration config, Expression> e) 69 | string TYPE.AnnotatedToCode(ExpressionToCodeConfiguration config, Expression expr) 70 | string TYPE.ToValuedCode(Expression> expression) 71 | string TYPE.GetNameIn(Expression> expression) 72 | string TYPE.GetNameIn(Expression expression) 73 | string TYPE.GetNameIn(Expression expr) 74 | 75 | class ExpressionToCodeConfiguration 76 | static readonly ExpressionToCodeConfiguration DefaultCodeGenConfiguration 77 | static readonly ExpressionToCodeConfiguration DefaultAssertionConfiguration 78 | static ExpressionToCodeConfiguration GlobalAssertionConfiguration 79 | static ExpressionToCodeConfiguration GlobalCodeGenConfiguration 80 | ExpressionToCodeConfiguration inst.WithCompiler(IExpressionCompiler compiler) 81 | ExpressionToCodeConfiguration inst.WithAnnotator(ICodeAnnotator annotator) 82 | ExpressionToCodeConfiguration inst.WithPrintedListLengthLimit(int? limitListsToLength) 83 | ExpressionToCodeConfiguration inst.WithMaximumValueLength(int? limitValueStringsToLength) 84 | ExpressionToCodeConfiguration inst.WithOmitImplicitCasts(bool omitImplicitCasts) 85 | ExpressionToCodeConfiguration inst.WithObjectStringifier(IObjectStringifier objectStringifier) 86 | ExpressionToCodeConfiguration inst.WithAlwaysUseExplicitTypeArguments(bool alwaysUseExplicitTypeArguments) 87 | IExpressionToCode inst.GetExpressionToCode() 88 | IAnnotatedToCode inst.GetAnnotatedToCode() 89 | 90 | class ExpressionToCodeExtensions 91 | string TYPE.ToCode(IExpressionToCode it, Expression> e) 92 | string TYPE.ToCode(IExpressionToCode it, Expression> e) 93 | string TYPE.ToCode(IExpressionToCode it, Expression> e) 94 | string TYPE.ToCode(IExpressionToCode it, Expression> e) 95 | 96 | class ExpressionTreeAssertion 97 | void TYPE.Assert(ExpressionToCodeConfiguration config, Expression> assertion, string msg) 98 | 99 | class ExpressionTreeCompilers 100 | static readonly IExpressionCompiler DotnetExpressionCompiler 101 | static readonly IExpressionCompiler FastExpressionCompiler 102 | 103 | class FormatStringParser 104 | (FormatStringParser.Segment[], string) TYPE.ParseFormatString(FormattableString formattableString) 105 | (FormatStringParser.Segment[], string) TYPE.ParseFormatString(string formatString, object[] formatArguments) 106 | 107 | struct FormatStringParser.Segment : ValueType 108 | string InitialStringPart 109 | object FollowedByValue 110 | string WithFormatString 111 | 112 | class EqualityExpressions 113 | EqualityExpressionClass TYPE.CheckForEquality(Expression> e) 114 | IEnumerable> TYPE.DisagreeingEqualities(ExpressionToCodeConfiguration config, Expression> e) 115 | 116 | class ObjectStringifierExtensions 117 | string TYPE.PlainObjectToCode(IObjectStringifier it, object val) 118 | 119 | class ObjectStringify 120 | static readonly IObjectStringifier Default 121 | static readonly IObjectStringifier WithFullTypeNames 122 | static readonly IObjectStringifier WithoutLiteralStrings 123 | static readonly IObjectStringifier WithFullTypeNamesWithoutLiteralStrings 124 | 125 | class ObjectToCode 126 | string TYPE.ComplexObjectToPseudoCode(object val) 127 | string TYPE.ComplexObjectToPseudoCode(ExpressionToCodeConfiguration config, object val) 128 | string TYPE.PlainObjectToCode(object val) 129 | string TYPE.PlainObjectToCode(object val, Type type) 130 | string TYPE.ToCSharpFriendlyTypeName(Type type) 131 | 132 | class PAssert 133 | void TYPE.IsTrue(Expression> assertion) 134 | void TYPE.That(Expression> assertion, string msg) 135 | 136 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ApiStabilityTest.UnstableApi.approved.txt: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /ExpressionToCodeTest/ApiStabilityTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public sealed class ApiStabilityTest 4 | { 5 | [Fact] 6 | public void PublicApi() 7 | { 8 | var publicTypes = typeof(ExpressionToCode).GetTypeInfo().Assembly.GetTypes() 9 | .Where(IsPublic) 10 | .Where(type => !type.Namespace!.Contains("Unstable")) 11 | .OrderByDescending(type => type.GetTypeInfo().IsEnum) 12 | .ThenByDescending(type => type.GetTypeInfo().IsInterface) 13 | .ThenBy(type => type.FullName); 14 | 15 | ApprovalTest.Verify(PrettyPrintTypes(publicTypes)); 16 | } 17 | 18 | [Fact] 19 | public void UnstableApi() 20 | { 21 | var unstableTypes = typeof(ExpressionToCode).GetTypeInfo().Assembly.GetTypes() 22 | .Where(IsPublic) 23 | .Where(type => type.Namespace!.Contains("Unstable")) 24 | .OrderByDescending(type => type.GetTypeInfo().IsEnum) 25 | .ThenByDescending(type => type.GetTypeInfo().IsInterface) 26 | .ThenBy(type => type.FullName); 27 | 28 | ApprovalTest.Verify(PrettyPrintTypes(unstableTypes)); 29 | } 30 | 31 | static string PrettyPrintTypes(IEnumerable types) 32 | => string.Join("", types.Select(PrettyPrintTypeDescription)); 33 | 34 | static string PrettyPrintTypeDescription(Type o) 35 | => PrettyPrintTypeHeader(o) + "\n" + PrettyPrintTypeContents(o); 36 | 37 | static string PrettyPrintTypeContents(Type type) 38 | { 39 | var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) 40 | .OrderBy(mi => mi.MetadataToken) 41 | .Where(mi => mi.DeclaringType!.GetTypeInfo().Assembly != typeof(object).GetTypeInfo().Assembly) //exclude noise 42 | ; 43 | 44 | var methodBlock = string.Join("", methods.Select(mi => PrettyPrintMethod(mi) + "\n")); 45 | 46 | var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) 47 | .Where(mi => mi.DeclaringType!.GetTypeInfo().Assembly != typeof(object).GetTypeInfo().Assembly) //exclude noise 48 | ; 49 | 50 | var fieldBlock = string.Join("", fields.Select(fi => PrettyPrintField(fi) + "\n")); 51 | 52 | return fieldBlock + methodBlock + "\n"; 53 | } 54 | 55 | static string PrettyPrintTypeHeader(Type type) 56 | { 57 | var prefix = TypePrefix(type); 58 | var baseType = type.GetTypeInfo().BaseType; 59 | var inheritanceTypes = baseType == typeof(object) || baseType == null 60 | ? type.GetInterfaces() 61 | : new[] { baseType }.Concat(type.GetInterfaces().Except(baseType.GetInterfaces())); 62 | var suffix = !inheritanceTypes.Any() || type.GetTypeInfo().IsEnum ? "" : " : " + string.Join(", ", inheritanceTypes.Select(ObjectToCode.ToCSharpFriendlyTypeName)); 63 | var name = type.ToCSharpFriendlyTypeName(); 64 | return prefix + " " + name + suffix; 65 | } 66 | 67 | static string TypePrefix(Type type) 68 | { 69 | if (type.GetTypeInfo().IsEnum) { 70 | return "enum"; 71 | } else if (type.GetTypeInfo().IsValueType) { 72 | return "struct"; 73 | } else if (type.GetTypeInfo().IsInterface) { 74 | return "interface"; 75 | } else { 76 | return "class"; 77 | } 78 | } 79 | 80 | static string PrettyPrintMethod(MethodInfo mi) 81 | { 82 | var fakeTarget = mi.IsStatic ? "TYPE" : "inst"; 83 | 84 | return " " + mi.ReturnType.ToCSharpFriendlyTypeName() + " " + fakeTarget + 85 | "." + mi.Name 86 | + PrettyPrintGenericArguments(mi) 87 | + PrettyPrintParameterList(mi); 88 | } 89 | 90 | static object PrettyPrintField(FieldInfo fi) 91 | => " " 92 | + (fi.IsLiteral ? "const " : (fi.IsStatic ? "static " : "") + (fi.IsInitOnly ? "readonly " : "")) 93 | + fi.FieldType.ToCSharpFriendlyTypeName() 94 | + " " + fi.Name 95 | + (fi.IsLiteral ? " = " + ObjectToCode.ComplexObjectToPseudoCode(fi.GetRawConstantValue()) : ""); 96 | 97 | static string PrettyPrintParameterList(MethodInfo mi) 98 | => "(" + string.Join( 99 | ", ", 100 | mi.GetParameters() 101 | .Select( 102 | pi => 103 | pi.ParameterType.ToCSharpFriendlyTypeName() + " " + pi.Name)) + ")"; 104 | 105 | static string PrettyPrintGenericArguments(MethodInfo mi) 106 | { 107 | if (!mi.IsGenericMethodDefinition) { 108 | return ""; 109 | } 110 | 111 | return "<" 112 | + string.Join(", ", mi.GetGenericArguments().Select(ObjectToCode.ToCSharpFriendlyTypeName)) 113 | + ">"; 114 | } 115 | 116 | static bool IsPublic(Type type) 117 | // ReSharper disable once ConstantNullCoalescingCondition 118 | => type.GetTypeInfo().IsPublic || type.GetTypeInfo().IsNestedPublic && IsPublic(type.DeclaringType ?? throw new InvalidOperationException("A nested public type has no declaring type" + type)); 119 | } 120 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ApprovalTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace ExpressionToCodeTest; 4 | 5 | static class ApprovalTest 6 | { 7 | public static void Verify(string text, [CallerFilePath] string? filePath = null, [CallerMemberName] string? memberName = null) 8 | { 9 | var filename = Path.GetFileNameWithoutExtension(filePath); 10 | var fileDir = Path.GetDirectoryName(filePath) ?? throw new InvalidOperationException("path " + filePath + " has no directory"); 11 | var approvalPath = Path.Combine(fileDir , filename + "." + memberName + ".approved.txt"); 12 | var isChanged = !File.Exists(approvalPath) || File.ReadAllText(approvalPath) != text; 13 | if (isChanged) { 14 | File.WriteAllText(approvalPath, text); 15 | throw new($"Approval changed; get git path: {approvalPath}"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ArrayAccessTests.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class ArrayAccessTests 4 | { 5 | [Fact] 6 | public void TestSingleDimensionalArrayIndexExpressionWithLambda() 7 | { 8 | var param = Expression.Parameter(typeof(string[]), "a"); 9 | var expr = Expression.Lambda( 10 | Expression.ArrayIndex(param, Expression.Constant(1)), 11 | param 12 | ); 13 | Assert.Equal( 14 | "a => a[1]", 15 | ExpressionToCode.ToCode(expr) 16 | ); 17 | } 18 | 19 | [Fact] 20 | public void TestSingleDimensionalArrayAccessExpressionWithLambda() 21 | { 22 | var param = Expression.Parameter(typeof(string[]), "a"); 23 | var expr = Expression.Lambda( 24 | Expression.ArrayAccess(param, Expression.Constant(1)), 25 | param 26 | ); 27 | Assert.Equal( 28 | "a => a[1]", 29 | ExpressionToCode.ToCode(expr) 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/BlockExpressionTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class BlockExpressionTest 4 | { 5 | [Fact] 6 | public void AddAssign() 7 | { 8 | var expr = Expression.AddAssign(Expression.Variable(typeof(int), "x"), Expression.Variable(typeof(int), "y")); 9 | Assert.Equal("x += y", ExpressionToCode.ToCode(expr)); 10 | } 11 | 12 | [Fact] 13 | public void AddAssignChained() 14 | { 15 | var xVar = Expression.Variable(typeof(int), "x"); 16 | var yVar = Expression.Variable(typeof(int), "y"); 17 | var expr = Expression.AddAssign(xVar, Expression.AddAssign(xVar, yVar)); 18 | Assert.Equal("x += (x += y)", ExpressionToCode.ToCode(expr)); 19 | //TODO: remove redundant parens 20 | } 21 | 22 | [Fact] 23 | public void AndAssign() 24 | { 25 | var expr = Expression.AndAssign(Expression.Variable(typeof(int), "x"), Expression.Variable(typeof(int), "y")); 26 | Assert.Equal("x &= y", ExpressionToCode.ToCode(expr)); 27 | } 28 | 29 | [Fact] 30 | public void AndAssignChained() 31 | { 32 | var xVar = Expression.Variable(typeof(int), "x"); 33 | var yVar = Expression.Variable(typeof(int), "y"); 34 | var expr = Expression.AndAssign(xVar, Expression.AndAssign(xVar, yVar)); 35 | Assert.Equal("x &= (x &= y)", ExpressionToCode.ToCode(expr)); 36 | //TODO: remove redundant parens 37 | } 38 | 39 | [Fact] 40 | public void PostDecrementAssign() 41 | { 42 | var expr = Expression.PostDecrementAssign(Expression.Variable(typeof(int), "x")); 43 | Assert.Equal("x--", ExpressionToCode.ToCode(expr)); 44 | } 45 | 46 | [Fact] 47 | public void PreDecrementAssign() 48 | { 49 | var expr = Expression.PreDecrementAssign(Expression.Variable(typeof(int), "x")); 50 | Assert.Equal("--x", ExpressionToCode.ToCode(expr)); 51 | } 52 | 53 | [Fact] 54 | public void PostIncrementAssign() 55 | { 56 | var expr = Expression.PostIncrementAssign(Expression.Variable(typeof(int), "x")); 57 | Assert.Equal("x++", ExpressionToCode.ToCode(expr)); 58 | } 59 | 60 | [Fact] 61 | public void PreIncrementAssign() 62 | { 63 | var expr = Expression.PreIncrementAssign(Expression.Variable(typeof(int), "x")); 64 | Assert.Equal("++x", ExpressionToCode.ToCode(expr)); 65 | } 66 | 67 | [Fact] 68 | public void SingleStatementBlockTest() 69 | { 70 | Expression> v = () => 1; 71 | Assert.Equal(@"{ () => 1; }", ExpressionToCode.ToCode(Expression.Block(typeof(void), v))); 72 | } 73 | 74 | [Fact] 75 | public void BlockVariablesTest() 76 | { 77 | var p = Expression.Parameter(typeof(int), "p"); 78 | var x = Expression.Parameter(typeof(int), "x"); 79 | Expression assignment = Expression.Assign(p, x); 80 | Assert.Equal(@"{ int p; int x; p = x; }", ExpressionToCode.ToCode(Expression.Block(typeof(void), new[] { p, x }, assignment))); 81 | } 82 | 83 | [Fact] 84 | public void BlockReturnTest() 85 | => Assert.Equal(@"{ return 1; }", ExpressionToCode.ToCode(Expression.Block(typeof(int), Expression.Constant(1)))); 86 | 87 | [Fact] 88 | public void VoidReturnBlockTest() 89 | => Assert.Equal(@"{ 1; }", ExpressionToCode.ToCode(Expression.Block(typeof(void), Expression.Constant(1)))); 90 | 91 | [Fact] 92 | public void MultipleStatementsBlockTest() 93 | { 94 | var p = Expression.Parameter(typeof(int), "p"); 95 | Expression assignment = Expression.Assign(p, Expression.Constant(1)); 96 | Expression addAssignment = Expression.AddAssign(p, Expression.Constant(5)); 97 | Assert.Equal(@"{ int p; p = 1; p += 5; }", ExpressionToCode.ToCode(Expression.Block(typeof(void), new[] { p }, assignment, addAssignment))); 98 | } 99 | 100 | [Fact] 101 | public void MultipleStatementWithReturnBlockTest() 102 | { 103 | var p = Expression.Parameter(typeof(int), "p"); 104 | Expression assignment = Expression.Assign(p, Expression.Constant(1)); 105 | Expression addAssignment = Expression.AddAssign(p, Expression.Constant(5)); 106 | Assert.Equal( 107 | @"{ int p; p = 1; return p += 5; }", 108 | ExpressionToCode.ToCode(Expression.Block(typeof(int), new[] { p }, assignment, addAssignment))); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/CSharpFriendlyTypeNameTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace ExpressionToCodeTest; 4 | 5 | public class CSharpFriendlyTypeNameTest 6 | { 7 | [Fact] 8 | public void SupportsBuiltInCases() 9 | { 10 | Assert.Equal("object", typeof(object).ToCSharpFriendlyTypeName()); 11 | Assert.Equal("string", typeof(string).ToCSharpFriendlyTypeName()); 12 | Assert.Equal("char", typeof(char).ToCSharpFriendlyTypeName()); 13 | Assert.Equal("byte", typeof(byte).ToCSharpFriendlyTypeName()); 14 | Assert.Equal("sbyte", typeof(sbyte).ToCSharpFriendlyTypeName()); 15 | Assert.Equal("short", typeof(short).ToCSharpFriendlyTypeName()); 16 | Assert.Equal("ushort", typeof(ushort).ToCSharpFriendlyTypeName()); 17 | Assert.Equal("int", typeof(int).ToCSharpFriendlyTypeName()); 18 | Assert.Equal("uint", typeof(uint).ToCSharpFriendlyTypeName()); 19 | Assert.Equal("long", typeof(long).ToCSharpFriendlyTypeName()); 20 | Assert.Equal("ulong", typeof(ulong).ToCSharpFriendlyTypeName()); 21 | Assert.Equal("void", typeof(void).ToCSharpFriendlyTypeName()); 22 | Assert.Equal("float", typeof(float).ToCSharpFriendlyTypeName()); 23 | Assert.Equal("decimal", typeof(decimal).ToCSharpFriendlyTypeName()); 24 | } 25 | 26 | [Fact] 27 | public void SupportsSimpleExamples() 28 | { 29 | Assert.Equal("DateTime", typeof(DateTime).ToCSharpFriendlyTypeName()); 30 | Assert.Equal("Regex", typeof(Regex).ToCSharpFriendlyTypeName()); 31 | Assert.Equal("ExpressionToCode", typeof(ExpressionToCode).ToCSharpFriendlyTypeName()); 32 | } 33 | 34 | [Fact] 35 | public void IntArray() 36 | => Assert.Equal("int[]", typeof(int[]).ToCSharpFriendlyTypeName()); 37 | 38 | [Fact] 39 | public void NullableValueType() 40 | => Assert.Equal("ConsoleKey?", typeof(ConsoleKey?).ToCSharpFriendlyTypeName()); 41 | 42 | [Fact] 43 | public void GenericList() 44 | => Assert.Equal("List", typeof(List).ToCSharpFriendlyTypeName()); 45 | 46 | [Fact] 47 | public void MultiDimArray() 48 | => Assert.Equal("string[,,]", typeof(string[,,]).ToCSharpFriendlyTypeName()); 49 | 50 | [Fact] //Has always been broken 51 | public void MultiDimOfSingleDimArray() 52 | => Assert.Equal("object[,][]", typeof(object[,][]).ToCSharpFriendlyTypeName()); 53 | 54 | [Fact] //Has always been broken 55 | public void SingleDimOfMultiDimArray() 56 | => Assert.Equal("object[][,]", typeof(object[][,]).ToCSharpFriendlyTypeName()); 57 | 58 | [Fact] //Has always been broken 59 | public void ConstructedSingleDimOfMultiDimArray() 60 | { 61 | // ReSharper disable once SuggestUseVarKeywordEvident 62 | // ReSharper disable once RedundantArrayCreationExpression 63 | var v = new[] { new object[2, 3] }; 64 | 65 | Assert.Equal("object[][,]", v.GetType().ToCSharpFriendlyTypeName()); 66 | } 67 | 68 | [Fact] 69 | public void ArrayGenericsMessyMix() 70 | => Assert.Equal("List[][]>[]", typeof(List[][]>[]).ToCSharpFriendlyTypeName()); 71 | 72 | [Fact] 73 | public void NestedClasses() 74 | => Assert.Equal("Outer.Nested", typeof(Outer.Nested).ToCSharpFriendlyTypeName()); 75 | 76 | [Fact] 77 | public void NestedNonGenericInGenericClasses() 78 | => Assert.Equal("Outer.Nested2", typeof(Outer.Nested2).ToCSharpFriendlyTypeName()); 79 | 80 | [Fact] 81 | public void NestedGenericInNonGenericClasses() 82 | => Assert.Equal("Outer2.Nested3", typeof(Outer2.Nested3).ToCSharpFriendlyTypeName()); 83 | 84 | [Fact] 85 | public void RussianDolls() 86 | => Assert.Equal("Tuple, Tuple>>", typeof(Tuple, Tuple>>).ToCSharpFriendlyTypeName()); 87 | 88 | [Fact] 89 | public void GenericArgumentTypes() 90 | => Assert.Equal("Func", typeof(Outer<,>.Nested<>).GetTypeInfo().GetMethod("Method")!.GetParameters()[0].ParameterType.ToCSharpFriendlyTypeName()); 91 | 92 | [Fact] 93 | public void UnboundNested() 94 | => Assert.Equal("Outer.Nested", typeof(Outer<,>.Nested<>).ToCSharpFriendlyTypeName()); 95 | 96 | [Fact] 97 | public void UnboundGenericList() 98 | => Assert.Equal("List", typeof(List<>).ToCSharpFriendlyTypeName()); 99 | 100 | [Fact] 101 | public void UnboundGenericListInTypeof() 102 | => Assert.Equal("() => typeof(List<>)", ExpressionToCode.ToCode(() => typeof(List<>))); 103 | 104 | [Fact] 105 | public void UnboundGenericNullableInTypeof() 106 | => Assert.Equal("() => typeof(Nullable<>)", ExpressionToCode.ToCode(() => typeof(Nullable<>))); 107 | 108 | [Fact] 109 | public void UnboundNestedInTypeof() 110 | => Assert.Equal("() => typeof(Outer<,>.Nested<>)", ExpressionToCode.ToCode(() => typeof(Outer<,>.Nested<>))); 111 | } 112 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/EnumTests.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public enum SomeEnum 4 | { 5 | A, 6 | B 7 | } 8 | 9 | [Flags] 10 | public enum SomeFlagsEnum 11 | { 12 | None = 0, 13 | A = 1, 14 | B = 2, 15 | AB = 3, 16 | C = 4, 17 | } 18 | 19 | public class EnumTests 20 | { 21 | [Fact] 22 | public void EnumConstant() 23 | => Assert.Equal( 24 | @"() => new object().Equals(MidpointRounding.ToEven)", 25 | ExpressionToCode.ToCode(() => new object().Equals(MidpointRounding.ToEven))); 26 | 27 | [Fact] 28 | public void EnumVariables() 29 | { 30 | var a = SomeEnum.A; 31 | var b = SomeEnum.B; 32 | Assert.Equal( 33 | @"() => a == b", 34 | ExpressionToCode.ToCode(() => a == b)); 35 | } 36 | 37 | [Fact] 38 | public void NullableEnumVariables() 39 | { 40 | var a = SomeEnum.A; 41 | SomeEnum? b = SomeEnum.B; 42 | Assert.Equal( 43 | @"() => a == b", 44 | ExpressionToCode.ToCode(() => a == b)); 45 | } 46 | 47 | [Fact] 48 | public void EnumVarEqConstant() 49 | { 50 | var a = SomeEnum.A; 51 | Assert.Equal( 52 | @"() => a == SomeEnum.B", 53 | ExpressionToCode.ToCode(() => a == SomeEnum.B)); 54 | Assert.Equal( 55 | @"() => SomeEnum.B == a", 56 | ExpressionToCode.ToCode(() => SomeEnum.B == a)); 57 | } 58 | 59 | [Fact] 60 | public void NullableEnumVarEqConstant() 61 | { 62 | SomeEnum? a = SomeEnum.A; 63 | Assert.Equal( 64 | @"() => a == SomeEnum.B", 65 | ExpressionToCode.ToCode(() => a == SomeEnum.B)); 66 | Assert.Equal( 67 | @"() => SomeEnum.B == a", 68 | ExpressionToCode.ToCode(() => SomeEnum.B == a)); 69 | } 70 | 71 | [Fact] 72 | public void EnumVarNeqConstant() 73 | { 74 | var a = SomeEnum.A; 75 | Assert.Equal( 76 | @"() => a != SomeEnum.B", 77 | ExpressionToCode.ToCode(() => a != SomeEnum.B)); 78 | Assert.Equal( 79 | @"() => SomeEnum.B != a", 80 | ExpressionToCode.ToCode(() => SomeEnum.B != a)); 81 | } 82 | 83 | [Fact] 84 | public void NullableEnumVarNeqConstant() 85 | { 86 | SomeEnum? a = SomeEnum.A; 87 | Assert.Equal( 88 | @"() => a != SomeEnum.B", 89 | ExpressionToCode.ToCode(() => a != SomeEnum.B)); 90 | Assert.Equal( 91 | @"() => SomeEnum.B != a", 92 | ExpressionToCode.ToCode(() => SomeEnum.B != a)); 93 | } 94 | 95 | [Fact] 96 | public void EnumVarLtConstant() 97 | { 98 | var a = SomeEnum.A; 99 | Assert.Equal( 100 | @"() => a < SomeEnum.B", 101 | ExpressionToCode.ToCode(() => a < SomeEnum.B)); 102 | Assert.Equal( 103 | @"() => SomeEnum.B > a", 104 | ExpressionToCode.ToCode(() => SomeEnum.B > a)); 105 | } 106 | 107 | [Fact] 108 | public void NullableEnumVarLtConstant() 109 | { 110 | SomeEnum? a = SomeEnum.A; 111 | Assert.Equal( 112 | @"() => a < SomeEnum.B", 113 | ExpressionToCode.ToCode(() => a < SomeEnum.B)); 114 | Assert.Equal( 115 | @"() => SomeEnum.B > a", 116 | ExpressionToCode.ToCode(() => SomeEnum.B > a)); 117 | } 118 | 119 | [Fact] 120 | public void EnumCornerCases() 121 | { 122 | var a = SomeEnum.A; 123 | var b = SomeFlagsEnum.B; 124 | Assert.Equal( 125 | @"() => a == SomeEnum.B", 126 | //C# compiler does not preserve this type information. 127 | ExpressionToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.A)); 128 | Assert.Equal( 129 | @"() => a == ((SomeEnum)4)", 130 | //C# compiler does not preserve this type information; requires cast 131 | ExpressionToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.C)); 132 | Assert.Equal( 133 | @"() => a == (SomeEnum)b", 134 | //but it does here! 135 | ExpressionToCode.ToCode(() => a == (SomeEnum)b)); 136 | Assert.Equal( 137 | @"() => (SomeFlagsEnum)a == b", 138 | //but it does here! 139 | ExpressionToCode.ToCode(() => (SomeFlagsEnum)a == b)); 140 | } 141 | 142 | [Fact] 143 | public void NullableEnumCornerCases() 144 | { 145 | SomeEnum? a = SomeEnum.A; 146 | SomeFlagsEnum? b = SomeFlagsEnum.B; 147 | 148 | Assert.Equal( 149 | @"() => a == SomeEnum.B", 150 | //C# 6 compiler does not preserve this type information. 151 | ExpressionToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.A)); 152 | Assert.Equal( 153 | @"() => a == ((SomeEnum)4)", 154 | //C# 6 compiler does not preserve this type information; requires cast 155 | ExpressionToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.C)); 156 | Assert.Equal( 157 | @"() => a == (SomeEnum)b", 158 | //but it does here! 159 | ExpressionToCode.ToCode(() => a == (SomeEnum)b)); 160 | Assert.Equal( 161 | @"() => (SomeFlagsEnum?)a == b", 162 | //but it does here! 163 | ExpressionToCode.ToCode(() => (SomeFlagsEnum?)a == b)); 164 | } 165 | 166 | [Fact] 167 | public void NullableEnumCornerCases_FullNames() 168 | { 169 | SomeEnum? a = SomeEnum.A; 170 | SomeFlagsEnum? b = SomeFlagsEnum.B; 171 | 172 | var exprToCode = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithObjectStringifier(ObjectStringify.WithFullTypeNames).GetExpressionToCode(); 173 | 174 | Assert.Equal( 175 | @"() => a == ExpressionToCodeTest.SomeEnum.B", 176 | //C# compiler does not preserve this type information. 177 | exprToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.A)); 178 | Assert.Equal( 179 | @"() => a == ((ExpressionToCodeTest.SomeEnum)4)", 180 | //C# compiler does not preserve this type information; requires cast 181 | exprToCode.ToCode(() => a == (SomeEnum)SomeFlagsEnum.C)); 182 | Assert.Equal( 183 | @"() => a == (ExpressionToCodeTest.SomeEnum)b", 184 | //but it does here! 185 | exprToCode.ToCode(() => a == (SomeEnum)b)); 186 | Assert.Equal( 187 | @"() => (ExpressionToCodeTest.SomeFlagsEnum?)a == b", 188 | //but it does here! 189 | exprToCode.ToCode(() => (SomeFlagsEnum?)a == b)); 190 | } 191 | 192 | [Fact] 193 | public void FlagsEnumConstant() 194 | { 195 | var ab = SomeFlagsEnum.A | SomeFlagsEnum.B; 196 | Assert.Equal( 197 | @"() => ab == SomeFlagsEnum.AB", 198 | ExpressionToCode.ToCode(() => ab == SomeFlagsEnum.AB)); 199 | } 200 | 201 | [Fact] 202 | public void NullableFlagsEnumConstant() 203 | { 204 | SomeFlagsEnum? ab = SomeFlagsEnum.A | SomeFlagsEnum.B; 205 | Assert.Equal( 206 | @"() => ab == SomeFlagsEnum.AB", 207 | ExpressionToCode.ToCode(() => ab == SomeFlagsEnum.AB)); 208 | } 209 | 210 | [Fact] 211 | public void FlagsEnumOr() 212 | { 213 | var a = SomeFlagsEnum.A; 214 | var b = SomeFlagsEnum.B; 215 | Assert.Equal( 216 | @"() => (SomeFlagsEnum)(a | b) == SomeFlagsEnum.AB", 217 | ExpressionToCode.ToCode(() => (a | b) == SomeFlagsEnum.AB)); //would be nice if this worked better, but not critical 218 | } 219 | 220 | [Fact] 221 | public void NullableFlagsEnumOr() 222 | { 223 | var a = SomeFlagsEnum.A; 224 | SomeFlagsEnum? b = SomeFlagsEnum.B; 225 | Assert.Equal( 226 | @"() => (SomeFlagsEnum?)(a | b) == SomeFlagsEnum.AB", 227 | ExpressionToCode.ToCode(() => (a | b) == SomeFlagsEnum.AB)); 228 | } 229 | 230 | [Fact] 231 | public void FlagsEnumComplexConstant() 232 | { 233 | var abc = SomeFlagsEnum.A | SomeFlagsEnum.B | SomeFlagsEnum.C; 234 | Assert.Equal( 235 | @"() => abc == (SomeFlagsEnum.AB | SomeFlagsEnum.C)", 236 | ExpressionToCode.ToCode(() => abc == (SomeFlagsEnum.AB | SomeFlagsEnum.C))); 237 | } 238 | 239 | [Fact] 240 | public void NullableFlagsEnumComplexConstant() 241 | { 242 | SomeFlagsEnum? abc = SomeFlagsEnum.A | SomeFlagsEnum.B | SomeFlagsEnum.C; 243 | Assert.Equal( 244 | @"() => abc == (SomeFlagsEnum.AB | SomeFlagsEnum.C)", 245 | ExpressionToCode.ToCode(() => abc == (SomeFlagsEnum.AB | SomeFlagsEnum.C))); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/EnumerableFormattingTest.NestedArraysUseProperConfig.approved.txt: -------------------------------------------------------------------------------- 1 | new[] { 2 | null, 3 | new { 4 | A = 3, 5 | B = new[] { 1, 2, 3, ... }, 6 | ..., 7 | } -------------------------------------------------------------------------------- /ExpressionToCodeTest/EnumerableFormattingTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class EnumerableFormattingTest 4 | { 5 | [Fact] 6 | public void ShortArraysRenderedCompletely() 7 | => Assert.Equal("new[] { 1, 2, 3 }", ObjectToCode.ComplexObjectToPseudoCode(new[] { 1, 2, 3 })); 8 | 9 | [Fact] 10 | public void ShortEnumerablesRenderedCompletely() 11 | => Assert.Equal("{ 1, 2, 3 }", ObjectToCode.ComplexObjectToPseudoCode(Enumerable.Range(1, 3))); 12 | 13 | [Fact] 14 | public void LongEnumerablesBreakAfter10_InCodeGen() 15 | => Assert.Equal( 16 | "{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }", 17 | ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.ComplexObjectToPseudoCode(Enumerable.Range(1, 18))); 18 | 19 | [Fact] 20 | public void LongArraysBreakAfter30_InAssertions() 21 | => Assert.Equal( 22 | "new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, ... }", 23 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.ComplexObjectToPseudoCode(Enumerable.Range(1, 31).ToArray())); 24 | 25 | [Fact] 26 | public void LongArraysDoNotBreakAfter30_InCodeGen() 27 | => Assert.Equal("new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }", ObjectToCode.ComplexObjectToPseudoCode(Enumerable.Range(1, 32).ToArray())); 28 | 29 | [Fact] 30 | public void LongArraysDoNotBreakIfSoConfigured() 31 | { 32 | var config = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithPrintedListLengthLimit(null); 33 | Assert.Equal("new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }", config.ComplexObjectToPseudoCode(Enumerable.Range(1, 13).ToArray())); 34 | } 35 | 36 | [Fact] 37 | public void EnumerableElisionIsConfigurable() 38 | { 39 | var config = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithPrintedListLengthLimit(3); 40 | Assert.Equal("{ 1, 2, 3, ... }", config.ComplexObjectToPseudoCode(Enumerable.Range(1, 13))); 41 | } 42 | 43 | [Fact] 44 | public void InsertsLineBreaksWhenNecessary() 45 | => Assert.Equal( 46 | @"{ 47 | ""12345000"", 48 | ""12345001"", 49 | ""12345002"", 50 | ""12345003"", 51 | ""12345004"", 52 | ""12345005"", 53 | ""12345006"", 54 | ""12345007"", 55 | ""12345008"", 56 | ""12345009"", 57 | ... 58 | }".Replace("\r", ""), 59 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithPrintedListLengthLimit(10).ComplexObjectToPseudoCode( 60 | Enumerable.Range(12345000, 13).Select(i => i.ToString(CultureInfo.InvariantCulture)))); 61 | 62 | [Fact] 63 | public void InsertsLineBreaksContentsContainLineBreaks() 64 | => Assert.Equal( 65 | @"{ 66 | new { 67 | A = 3, 68 | B = 'b', 69 | }, 70 | new { 71 | A = 3, 72 | B = 'b', 73 | }, 74 | }".Replace("\r", ""), 75 | ObjectToCode.ComplexObjectToPseudoCode(Enumerable.Repeat(new { A = 3, B = 'b' }, 2))); 76 | 77 | [Fact] 78 | public void NestedArraysUseProperConfig() 79 | { 80 | var config = ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithPrintedListLengthLimit(3); 81 | ApprovalTest.Verify( 82 | config.ComplexObjectToPseudoCode( 83 | new[] { 84 | null, 85 | new { 86 | A = 3, 87 | B = new[] { 1, 2, 3, 4, 5 } 88 | } 89 | })); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/EqualityComparingTest.cs: -------------------------------------------------------------------------------- 1 | using ExpressionToCodeLib.Internal; 2 | 3 | #pragma warning disable 252,253 4 | 5 | // ReSharper disable NegativeEqualityExpression 6 | // ReSharper disable EqualExpressionComparison 7 | // ReSharper disable ConvertToConstant.Local 8 | // ReSharper disable RedundantCast 9 | namespace ExpressionToCodeTest; 10 | 11 | public sealed class EqualityComparingTest 12 | { 13 | static readonly string bla = "bla"; 14 | static readonly object bla2object = "bla2object"; 15 | static readonly string bla2string = "bla2string"; 16 | 17 | [Fact] 18 | public void EqualsOpDetected() 19 | { 20 | Assert.Equal(EqualityExpressionClass.EqualsOp, EqualityExpressions.CheckForEquality(() => bla == bla2object)); 21 | Assert.Equal(EqualityExpressionClass.EqualsOp, EqualityExpressions.CheckForEquality(() => new DateTime(2011, 05, 17) == DateTime.Today)); 22 | } 23 | 24 | [Fact] 25 | public void NotEqualsOpDetected() 26 | { 27 | Assert.Equal(EqualityExpressionClass.NotEqualsOp, EqualityExpressions.CheckForEquality(() => bla != bla2object)); 28 | Assert.Equal(EqualityExpressionClass.NotEqualsOp, EqualityExpressions.CheckForEquality(() => new DateTime(2011, 05, 17) != DateTime.Today)); 29 | } 30 | 31 | [Fact] 32 | public void EquatableDetected() 33 | { //TODO: makes interesting expressiontocode test too 34 | Assert.Equal(EqualityExpressionClass.EquatableEquals, EqualityExpressions.CheckForEquality(() => ((IEquatable)bla).Equals(bla2string))); 35 | Assert.Equal(EqualityExpressionClass.EquatableEquals, EqualityExpressions.CheckForEquality(() => bla.Equals(bla2string))); 36 | Assert.Equal(EqualityExpressionClass.EquatableEquals, EqualityExpressions.CheckForEquality(() => new DateTime(2011, 05, 17).Equals(DateTime.Today))); 37 | } 38 | 39 | [Fact] 40 | public void NoneDetected() 41 | { 42 | Assert.Equal(EqualityExpressionClass.None, EqualityExpressions.CheckForEquality(() => bla.StartsWith("bla", StringComparison.Ordinal))); 43 | Assert.Equal(EqualityExpressionClass.None, EqualityExpressions.CheckForEquality(() => !(bla == bla2object))); 44 | Assert.Equal(EqualityExpressionClass.None, EqualityExpressions.CheckForEquality(() => DateTime.Equals(new DateTime(2011, 05, 17), DateTime.Today))); 45 | // no match since specific method 46 | Assert.Equal(EqualityExpressionClass.None, EqualityExpressions.CheckForEquality(() => !ReferenceEquals(null, null))); 47 | } 48 | 49 | [Fact] 50 | public void ObjectEqualsDetected() 51 | { 52 | Assert.Equal(EqualityExpressionClass.ObjectEquals, EqualityExpressions.CheckForEquality(() => bla2object.Equals(bla2object))); 53 | var anon = new { X = bla, Y = bla2object }; 54 | Assert.Equal(EqualityExpressionClass.ObjectEquals, EqualityExpressions.CheckForEquality(() => anon.Equals(anon))); 55 | } 56 | 57 | [Fact] 58 | public void ObjectEqualsStaticDetected() 59 | { 60 | Assert.Equal(EqualityExpressionClass.ObjectEqualsStatic, EqualityExpressions.CheckForEquality(() => Equals(bla, null))); 61 | Assert.Equal(EqualityExpressionClass.ObjectEqualsStatic, EqualityExpressions.CheckForEquality(() => Equals(null, 42))); 62 | } 63 | 64 | [Fact] 65 | public void ObjectReferenceEqualsDetected() 66 | { 67 | Assert.Equal(EqualityExpressionClass.ObjectReferenceEquals, EqualityExpressions.CheckForEquality(() => ReferenceEquals(bla2object, bla2object))); 68 | Assert.Equal(EqualityExpressionClass.ObjectReferenceEquals, EqualityExpressions.CheckForEquality(() => ReferenceEquals(null, null))); 69 | } 70 | 71 | [Fact] 72 | public void SequenceEqualsDetected() 73 | { 74 | Assert.Equal(EqualityExpressionClass.SequenceEqual, EqualityExpressions.CheckForEquality(() => bla.AsEnumerable().SequenceEqual(bla2string))); 75 | Assert.Equal(EqualityExpressionClass.SequenceEqual, EqualityExpressions.CheckForEquality(() => new[] { 'b', 'l', 'a' }.SequenceEqual(bla2string))); 76 | } 77 | 78 | static (EqualityExpressionClass eqClass, bool)[] EqClasses(params EqualityExpressionClass[] classes) 79 | => classes.Select(eqClass => (eqClass, false)).ToArray(); 80 | 81 | [Fact] 82 | public void StringEqDisagreement() 83 | { 84 | var equalities1 = EqualityExpressions.DisagreeingEqualities(ExpressionToCodeConfiguration.DefaultAssertionConfiguration, () => ReferenceEquals(1000.ToString(CultureInfo.InvariantCulture), 10 + "00")) 85 | ?? throw new("Expected non-null return"); 86 | 87 | Assert.Equal( 88 | equalities1.OrderBy(x => x), 89 | EqClasses( 90 | EqualityExpressionClass.EqualsOp, 91 | EqualityExpressionClass.NotEqualsOp, 92 | EqualityExpressionClass.ObjectEquals, 93 | EqualityExpressionClass.ObjectEqualsStatic, 94 | EqualityExpressionClass.EquatableEquals, 95 | EqualityExpressionClass.SequenceEqual, 96 | EqualityExpressionClass.StructuralEquals 97 | ) 98 | .OrderBy(x => x)); 99 | 100 | var equalities2 = EqualityExpressions.DisagreeingEqualities(ExpressionToCodeConfiguration.DefaultAssertionConfiguration, () => 1000.ToString(CultureInfo.InvariantCulture).Equals(10 + "00")) 101 | ?? throw new("Expected non-null return"); 102 | Assert.Equal( 103 | equalities2.ToArray(), 104 | EqClasses(EqualityExpressionClass.ObjectReferenceEquals)); 105 | } 106 | 107 | [Fact] 108 | public void DtRefEqDisagreement() 109 | #pragma warning disable CA2013 // Do not use ReferenceEquals with value types 110 | // ReSharper disable ReferenceEqualsWithValueType 111 | => Assert.Equal( 112 | EqualityExpressions.DisagreeingEqualities( 113 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration, 114 | () => ReferenceEquals(new DateTime(2011, 05, 17), new DateTime(2011, 05, 17))) 115 | ?.ToArray() ?? throw new("Expected non-null return"), 116 | EqClasses( 117 | EqualityExpressionClass.ObjectEquals, 118 | EqualityExpressionClass.ObjectEqualsStatic, 119 | EqualityExpressionClass.StructuralEquals 120 | )); 121 | // ReSharper restore ReferenceEqualsWithValueType 122 | #pragma warning restore CA2013 // Do not use ReferenceEquals with value types 123 | 124 | [Fact] 125 | public void DtEqDisagreement() 126 | => Assert.Equal( 127 | EqualityExpressions.DisagreeingEqualities(ExpressionToCodeConfiguration.DefaultAssertionConfiguration, () => new DateTime(2011, 05, 17).Equals(new DateTime(2011, 05, 17)))?.ToArray() ?? throw new("Expected non-null return"), 128 | EqClasses(EqualityExpressionClass.ObjectReferenceEquals)); 129 | } 130 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ExceptionsSerialization.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | //requires binary serialization, which is omitted in older .net cores - but those are out of support: https://docs.microsoft.com/en-us/lifecycle/products/microsoft-net-and-net-core 4 | 5 | namespace ExpressionToCodeTest; 6 | 7 | public class ExceptionsSerialization 8 | { 9 | [MethodImpl(MethodImplOptions.NoInlining)] 10 | static void IntentionallyFailingMethod() 11 | => PAssert.That(() => false); 12 | 13 | [Fact] 14 | public void PAssertExceptionIsSerializable() 15 | => AssertMethodFailsWithSerializableException(IntentionallyFailingMethod); 16 | 17 | #pragma warning disable SYSLIB0011 // BinaryFormatter is Obsolete 18 | static void AssertMethodFailsWithSerializableException(Action intentionallyFailingMethod) 19 | { 20 | var original = Assert.ThrowsAny(intentionallyFailingMethod); 21 | var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 22 | var ms = new MemoryStream(); 23 | formatter.Serialize(ms, original); 24 | var deserialized = formatter.Deserialize(new MemoryStream(ms.ToArray())); 25 | Assert.Equal(original.ToString(), deserialized.ToString()); 26 | } 27 | #pragma warning restore SYSLIB0011 28 | } 29 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ExpressionInterpretationBug.cs: -------------------------------------------------------------------------------- 1 | //requires .net 4.7.2, but not testing older than 4.8 anymore; so this is effectively always on 2 | 3 | using FastExpressionCompiler; 4 | 5 | namespace ExpressionToCodeTest; 6 | 7 | /// 8 | /// Repro for https://github.com/dotnet/runtime/issues/29673 9 | /// 10 | public class ExpressionInterpretationBug 11 | { 12 | struct AStruct 13 | { 14 | public int AValue; 15 | } 16 | 17 | class SomethingMutable 18 | { 19 | public AStruct AStructField; 20 | } 21 | 22 | static Expression> Expr(Expression> e) 23 | => e; 24 | 25 | [Fact] 26 | public void MemberMemberBindingForStructsCompilesOk() 27 | { 28 | var expr_compiled = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).Compile(false)(); 29 | Assert.Equal(2, expr_compiled.AStructField.AValue); 30 | } 31 | #if!NETFRAMEWORK 32 | [Fact] 33 | public void MemberMemberBindingForStructsInterpretsWrong_BUGBUG() 34 | { 35 | var expr_interpreted = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).Compile(true)(); 36 | //Assert.Equal(2, expr_interpreted.AStructField.AValue); //this should hold, but instead: 37 | Assert.Equal(0, expr_interpreted.AStructField.AValue); //this way I might notice when the bug gets fixed (https://github.com/dotnet/runtime/issues/29673) 38 | } 39 | #endif 40 | 41 | [Fact] 42 | public void MemberMemberBindingForStructsFastCompilesOk() 43 | { 44 | var expr_compile_fast = Expr(() => new SomethingMutable { AStructField = { AValue = 2 } }).CompileFast()(); 45 | Assert.Equal(2, expr_compile_fast.AStructField.AValue); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ExpressionToCodeTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | library 4 | net48;net8.0 5 | latest 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ExpressionWithNameTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class ExpressionWithNameTest 4 | { 5 | [Fact] 6 | public void TheVariable_ToNameOf() 7 | { 8 | var theVariable = "theValue"; 9 | var actual = ExpressionToCode.GetNameIn(() => theVariable); 10 | Assert.Equal("theVariable", actual); 11 | } 12 | 13 | [Fact] 14 | public void TheMethod_ToNameOf() 15 | { 16 | var x = 1; 17 | var y = ""; 18 | var actual = ExpressionToCode.GetNameIn(() => TheComplexMethod(x, y)); 19 | Assert.Equal("TheComplexMethod", actual); 20 | } 21 | 22 | [Fact] 23 | public void TheMethod_ToNameOf_asVariable() 24 | { 25 | Expression> theComplexMethod = (x, y) => TheComplexMethod(x, y); 26 | var actual = ExpressionToCode.GetNameIn(theComplexMethod); 27 | Assert.Equal("TheComplexMethod", actual); 28 | 29 | var full = ExpressionToCode.ToCode(theComplexMethod.Body); 30 | Assert.NotEqual(full, actual); 31 | } 32 | 33 | [Fact] 34 | public void TheMethod_ToNameOf_withValues() 35 | { 36 | var actual = ExpressionToCode.GetNameIn(() => TheComplexMethod(1, "2")); 37 | Assert.Equal("TheComplexMethod", actual); 38 | } 39 | 40 | [Fact] 41 | public void TheGenericMethod_ToNameOf() 42 | { 43 | var actual = ExpressionToCode.GetNameIn(() => TheGenericMethod(2)); 44 | Assert.Equal("TheGenericMethod", actual); 45 | } 46 | 47 | [Fact] 48 | public void TheProperty_ToNameOf() 49 | { 50 | var actual = ExpressionToCode.GetNameIn(() => TheProperty); 51 | Assert.Equal("TheProperty", actual); 52 | } 53 | 54 | [Fact] 55 | public void TheSimpleMethod_ToNameOf() 56 | { 57 | var actual = ExpressionToCode.GetNameIn(() => TheSimpleMethod()); 58 | Assert.Equal("TheSimpleMethod", actual); 59 | } 60 | 61 | // ReSharper disable once MemberCanBeMadeStatic.Local 62 | void TheSimpleMethod() { } 63 | static string TheProperty => "TheValue"; 64 | 65 | static string TheComplexMethod(int parameter1, string parameter2) 66 | => "TheMethod " + parameter1 + " " + parameter2; 67 | 68 | static string TheGenericMethod(int two) 69 | => "Return value is " + two * two + typeof(T).Name; 70 | } 71 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System; 4 | global using System.Collections; 5 | global using System.Collections.Generic; 6 | global using System.Globalization; 7 | global using System.Linq; 8 | global using System.Linq.Expressions; 9 | global using System.Reflection; 10 | global using System.Runtime.CompilerServices; 11 | global using ExpressionToCodeLib; 12 | global using JetBrains.Annotations; 13 | global using Xunit; 14 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ImplicitCastingTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class ImplicitCastingTest 4 | { 5 | [Fact] 6 | public void CharNoCast() 7 | => Assert.Equal( 8 | @"() => ""abc""[1] == 'b'", 9 | ExpressionToCode.ToCode(() => "abc"[1] == 'b')); 10 | 11 | [Fact] 12 | public void CharComp() 13 | { 14 | var c = 'c'; 15 | Assert.Equal( 16 | @"() => c == 'b'", 17 | ExpressionToCode.ToCode(() => c == 'b')); 18 | } 19 | 20 | [Fact] 21 | public void DecimalImplicitCast() 22 | { 23 | var i = 1; 24 | Assert.Equal( 25 | @"() => 1m + -i > 0m || false", 26 | // ReSharper disable once RedundantLogicalConditionalExpressionOperand 27 | ExpressionToCode.ToCode(() => 1m + -i > 0m || false)); 28 | } 29 | 30 | [Fact] 31 | public void StringImplicitConcat() 32 | { 33 | var i = 1; 34 | var x = "X"; 35 | Assert.Equal( 36 | @"() => 1m + x + ""!!"" + i", 37 | ExpressionToCode.ToCode(() => 1m + x + "!!" + i)); 38 | } 39 | 40 | [Fact] 41 | public void NotImplicitCast() 42 | { 43 | byte z = 42; 44 | Assert.Equal( 45 | @"() => ~z == 0", 46 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse 47 | ExpressionToCode.ToCode(() => ~z == 0)); 48 | } 49 | 50 | [Fact] 51 | public void AvoidsImplicitBoxingWhenTargetTypeIsAGenericArgument() 52 | => Assert.Equal( 53 | @"() => StaticTestClass.TwoArgsTwoGeneric(3, new object())", 54 | ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsTwoGeneric(3, new object())) 55 | ); 56 | 57 | [Fact] 58 | public void AvoidsImplicitCastWhenTargetTypeIsAGenericArgument() 59 | { 60 | var x = 37; 61 | var y = 42.0; 62 | 63 | Assert.Equal( 64 | @"() => StaticTestClass.TwoArgsTwoGeneric(x, y)", 65 | ExpressionToCode.ToCode(() => StaticTestClass.TwoArgsTwoGeneric(x, y)) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/JunkClassesForTesting.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | // ReSharper disable UnusedTypeParameter 4 | // ReSharper disable ClassNeverInstantiated.Global 5 | public class Outer 6 | { 7 | public class Nested 8 | { 9 | [UsedImplicitly] 10 | public void Method(Func arg) { } 11 | } 12 | 13 | public class Nested2 { } 14 | } 15 | 16 | public class Outer2 17 | { 18 | public class Nested3 19 | { 20 | [UsedImplicitly] 21 | public void Method(Func arg) { } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/MultipleIndexerTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | sealed class HasIndexers 4 | { 5 | public object? this[string s] => null; 6 | public object? this[int i] => null; 7 | } 8 | 9 | public sealed class MultipleIndexerTest 10 | { 11 | [Fact] 12 | public void CanPrettyPrintVariousIndexers() 13 | => Assert.Equal( 14 | "() => new HasIndexers()[3] == new HasIndexers()[\"three\"]", 15 | ExpressionToCode.ToCode(() => new HasIndexers()[3] == new HasIndexers()["three"]) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/NestedClassTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class Parent 4 | { 5 | public class Nested { } 6 | 7 | // ReSharper disable once UnusedTypeParameter 8 | public class NestedGen { } 9 | } 10 | 11 | // ReSharper disable once UnusedTypeParameter 12 | public class ParentGen 13 | { 14 | public class Nested { } 15 | 16 | // ReSharper disable once UnusedTypeParameter 17 | public class NestedGen { } 18 | } 19 | 20 | public class NestedClassTest 21 | { 22 | [Fact] 23 | public void PlainNested() 24 | => Assert.Equal( 25 | "() => null as Parent.Nested", 26 | ExpressionToCode.ToCode(() => null as Parent.Nested)); 27 | 28 | [Fact] 29 | public void GenericNested() 30 | { 31 | Assert.Equal("() => null as Parent.NestedGen", ExpressionToCode.ToCode(() => null as Parent.NestedGen)); 32 | Assert.Equal("() => null as Parent.NestedGen>", ExpressionToCode.ToCode(() => null as Parent.NestedGen>)); 33 | } 34 | 35 | [Fact] 36 | public void NestedInGeneric() 37 | { 38 | Assert.Equal("() => null as ParentGen.Nested", ExpressionToCode.ToCode(() => null as ParentGen.Nested)); 39 | Assert.Equal("() => null as ParentGen.Nested>.Nested", ExpressionToCode.ToCode(() => null as ParentGen.Nested>.Nested)); 40 | } 41 | 42 | [Fact] 43 | public void GenericNestedInGeneric() 44 | { 45 | Assert.Equal("() => null as ParentGen.NestedGen", ExpressionToCode.ToCode(() => null as ParentGen.NestedGen)); 46 | Assert.Equal( 47 | "() => null as ParentGen>.NestedGen", 48 | ExpressionToCode.ToCode(() => null as ParentGen>.NestedGen)); 49 | Assert.Equal( 50 | "() => null as ParentGen.NestedGen.Nested>", 51 | ExpressionToCode.ToCode(() => null as ParentGen.NestedGen.Nested>)); 52 | Assert.Equal( 53 | "() => null as ParentGen.Nested>.NestedGen.NestedGen>", 54 | ExpressionToCode.ToCode(() => null as ParentGen.Nested>.NestedGen.NestedGen>)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/PAssertTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class PAssertTest 4 | { 5 | static readonly ExpressionToCodeConfiguration config = 6 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithAnnotator(CodeAnnotators.ValuesOnStalksCodeAnnotator).WithCompiler(ExpressionTreeCompilers.DotnetExpressionCompiler); 7 | 8 | [Fact] 9 | public void TestBasicStalks() 10 | { 11 | var msgLines = PAssertLines( 12 | () => 13 | config.Assert( 14 | () => 15 | TimeSpan.FromMilliseconds(10.0).CompareTo(TimeSpan.FromMinutes(1.0)) > 0 16 | )); 17 | Assert.Equal(@"TimeSpan.FromMilliseconds(10.0).CompareTo(TimeSpan.FromMinutes(1.0)) > 0 : assertion failed", msgLines[0]); 18 | var expectedMessage = @" 19 | TimeSpan.FromMilliseconds(10.0).CompareTo(TimeSpan.FromMinutes(1.0)) > 0 : assertion failed 20 |                  │                  │                   │ 21 |                  │                  │                   00:01:00 22 |                  │                  -1 23 |                  00:00:00.0100000 24 | "; 25 | Assert.Equal(expectedMessage.Replace("\r", "").Trim(), string.Join("\n", msgLines)); 26 | } 27 | 28 | [Fact] 29 | public void NoValuesForBoringCasts() 30 | { 31 | var msgLines = PAssertLines( 32 | () => 33 | config.Assert( 34 | () => 35 | Equals(3, 4) 36 | )); 37 | Assert.Contains("failed", msgLines[0]); 38 | Assert.Contains("Equals", msgLines[0]); 39 | Assert.Single(msgLines); 40 | } 41 | 42 | [Fact] 43 | public void ValuesForNonBoringCasts() 44 | { 45 | var x = ulong.MaxValue; 46 | var msgLines = PAssertLines( 47 | () => config.Assert( 48 | () => 0 == (ulong)(uint)x 49 | )); 50 | Assert.Equal(@"0UL == (uint)x : assertion failed", (object)msgLines[0]); 51 | Assert.Equal(2, (object)msgLines[1].Count(c => c == '│')); 52 | } 53 | 54 | [Fact] 55 | public void AppendsAssertionFailedOnFailure() 56 | { 57 | var msgLines = PAssertLines(() => config.Assert(() => false)); 58 | Assert.Equal(@"false : assertion failed", (object)msgLines[0]); 59 | Assert.Equal(1, (object)msgLines.Length); 60 | } 61 | 62 | [Fact] 63 | public void AppendsSingleLineMessageOnFailure() 64 | { 65 | var msgLines = PAssertLines(() => config.Assert(() => false, "oops")); 66 | Assert.Equal(@"false : oops", (object)msgLines[0]); 67 | Assert.Equal(1, (object)msgLines.Length); 68 | } 69 | 70 | [Fact] 71 | public void AppendsSingleLineMessageWithNewlineOnFailure() 72 | { 73 | var msgLines = PAssertLines(() => config.Assert(() => false, "oops\n")); 74 | Assert.Equal(@"false : oops", (object)msgLines[0]); 75 | Assert.Equal(1, (object)msgLines.Length); 76 | } 77 | 78 | [Fact] 79 | public void AppendsSingleLineMessageBeforeStalks() 80 | { 81 | var x = 0; 82 | var msgLines = PAssertLines(() => config.Assert(() => x == 1, "oops\n")); 83 | Assert.Equal(@"x == 1 : oops", (object)msgLines[0]); 84 | Assert.Equal(3, (object)msgLines.Length); 85 | } 86 | 87 | [Fact] 88 | public void PrependsMultiLineMessage() 89 | { 90 | var x = 0; 91 | var msgLines = PAssertLines(() => config.Assert(() => x == 1, "oops\nagain")); 92 | Assert.Equal(@"oops", (object)msgLines[0]); 93 | Assert.Equal(@"again", (object)msgLines[1]); 94 | Assert.Equal(@"x == 1", (object)msgLines[2]); 95 | Assert.Equal(5, (object)msgLines.Length); 96 | } 97 | 98 | static string[] PAssertLines(Action action) 99 | { 100 | var exc = Assert.ThrowsAny(action); 101 | return exc.Message.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/StringInterpolationTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public sealed class StringInterpolationTest 4 | { 5 | // ReSharper disable once MemberCanBeMadeStatic.Local 6 | FormattableString Interpolation(FormattableString str) 7 | => str; 8 | 9 | [Fact] 10 | public void InterpolationWithoutArgumentsIsJustAString() 11 | => Assert.Equal( 12 | @"() => ""abc""", 13 | ExpressionToCode.ToCode(() => $"abc")); 14 | 15 | [Fact] 16 | public void ForcedInterpolationWorks() 17 | => Assert.Equal( 18 | @"() => Interpolation($""abc"")", 19 | ExpressionToCode.ToCode(() => Interpolation($"abc"))); 20 | 21 | [Fact] 22 | public void FormattableStringFactory_IsRenderedAsInterpolation() 23 | => Assert.Equal( 24 | @"() => Interpolation($""abc"")", 25 | ExpressionToCode.ToCode( 26 | () => Interpolation(FormattableStringFactory.Create("abc")) 27 | )); 28 | 29 | [Fact] 30 | public void FormattableStringFactory_NonConstantStringIsNoInterpolation() 31 | { 32 | var s = "abc"; 33 | Assert.Equal( 34 | @"() => Interpolation(FormattableStringFactory.Create(s, new object[] { }))", 35 | ExpressionToCode.ToCode( 36 | () => Interpolation(FormattableStringFactory.Create(s)) 37 | )); 38 | } 39 | 40 | [Fact] 41 | public void FormattableStringFactory_NonInlineArrayIsNoInterpolation() 42 | { 43 | var arr = new object[0]; 44 | 45 | Assert.Equal( 46 | @"() => Interpolation(FormattableStringFactory.Create(""abc"", arr))", 47 | ExpressionToCode.ToCode( 48 | () => Interpolation(FormattableStringFactory.Create("abc", arr)) 49 | )); 50 | } 51 | 52 | [Fact] 53 | public void ForcedInterpolationWithOneArg() 54 | => Assert.Equal( 55 | @"() => Interpolation($""abc {3.0f}"")", 56 | ExpressionToCode.ToCode(() => Interpolation($"abc {3f}"))); 57 | 58 | [Fact] 59 | public void ForcedInterpolationWithNestedString() 60 | => Assert.Equal( 61 | @"() => Interpolation($""abc {""def""}"")", 62 | ExpressionToCode.ToCode(() => Interpolation($"abc {"def"}"))); 63 | 64 | [Fact] 65 | public void ForcedInterpolationWithNestedInterpolation() 66 | => Assert.Equal( 67 | @"() => Interpolation($""abc {Interpolation($""abc {""def""}"")}"")", 68 | ExpressionToCode.ToCode( 69 | () => Interpolation($"abc {Interpolation($"abc {"def"}")}") 70 | ) 71 | ); 72 | 73 | [Fact] 74 | public void ForcedInterpolationWithTwoArguments() 75 | => Assert.Equal( 76 | @"() => Interpolation($""abc {3.0f} X {'a'} Y"")", 77 | ExpressionToCode.ToCode(() => Interpolation($"abc {3f} X {'a'} Y"))); 78 | 79 | [Fact] 80 | public void ForcedInterpolationWithAdditionInArgument() 81 | => Assert.Equal( 82 | @"() => Interpolation($""abc {""abc"".Length + 3.5} Z"")", 83 | ExpressionToCodeConfiguration.DefaultCodeGenConfiguration.WithOmitImplicitCasts(true).ToCode(() => Interpolation($"abc {"abc".Length + 3.5} Z"))); 84 | 85 | [Fact] 86 | public void ForcedInterpolationWithTernaryArgumentNeedsParens() 87 | { 88 | var aBoolean = true; 89 | 90 | Assert.Equal( 91 | @"() => Interpolation($""abc {(aBoolean ? 1 : 2)} Z"")", 92 | ExpressionToCode.ToCode(() => Interpolation($"abc {(aBoolean ? 1 : 2)} Z"))); 93 | } 94 | 95 | [Fact] 96 | public void ForcedInterpolationWithNewlinesInSubExprIsLiteral() 97 | => Assert.Equal( 98 | @"() => Interpolation($@""abc {new { I = @""1 99 | 2 100 | 3 101 | 4 102 | "", J = 1 }} Z"")", 103 | ExpressionToCode.ToCode( 104 | () => Interpolation( 105 | $@"abc {new { I = @"1 106 | 2 107 | 3 108 | 4 109 | ", J = 1 }} Z") 110 | )); 111 | 112 | [Fact] 113 | public void ForcedInterpolationWithFormatSpecifier() 114 | => Assert.Equal( 115 | @"() => Interpolation($""abc {DateTime.Now:somespecifier: yep!} Z"")", 116 | ExpressionToCode.ToCode(() => Interpolation($"abc {DateTime.Now:somespecifier: yep!} Z"))); 117 | 118 | [Fact] 119 | public void ForcedInterpolationWithCurlyBraces() 120 | => Assert.Equal( 121 | @"() => Interpolation($""abc {{!}}"")", 122 | ExpressionToCode.ToCode(() => Interpolation($"abc {{!}}"))); 123 | 124 | [Fact] 125 | public void InterpolationWithOneArg() 126 | => Assert.Equal( 127 | @"() => $""abc {3.0f}""", 128 | ExpressionToCode.ToCode(() => $"abc {3f}")); 129 | 130 | [Fact] 131 | public void InterpolationWithTwoArgs() 132 | => Assert.Equal( 133 | @"() => $""abc {42} def {""ghi""}""", 134 | ExpressionToCode.ToCode(() => $"abc {42} def {"ghi"}")); 135 | 136 | [Fact] 137 | public void InterpolationWithThreeArgs() 138 | { 139 | var jkl = 123; 140 | Assert.Equal( 141 | @"() => $""abc {37} def {null} ghi {jkl} mno""", 142 | ExpressionToCode.ToCode(() => $"abc {37} def {null} ghi {jkl} mno")); 143 | } 144 | 145 | [Fact] 146 | public void InterpolationWithFourArgs() 147 | { 148 | var jkl = 123; 149 | var p = 2; 150 | var q = 3; 151 | var r = 4; 152 | Assert.Equal( 153 | @"() => $""abc {37} def {null} ghi {jkl} mno {p + q + r} stu""", 154 | ExpressionToCode.ToCode(() => $"abc {37} def {null} ghi {jkl} mno {p + q + r} stu")); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExprExceptionTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class FailingClass 4 | { 5 | [MethodImpl(MethodImplOptions.NoInlining)] 6 | public static bool SomeFunction() 7 | => throw new Exception(); 8 | 9 | [MethodImpl(MethodImplOptions.NoInlining)] 10 | public static bool SomeWrappedFunction() 11 | => SomeFunction(); 12 | } 13 | 14 | public class SubExprExceptionTest 15 | { 16 | [Fact] 17 | public void ExceptionDoesntCauseFailure() 18 | => Assert.Equal( 19 | @"() => FailingClass.SomeWrappedFunction() 20 | FailingClass.SomeWrappedFunction() 21 | → throws System.Exception 22 | 23 | ".Replace("\r\n", "\n"), 24 | ExpressionToCode.AnnotatedToCode(() => FailingClass.SomeWrappedFunction())); 25 | } 26 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.A_plus_B_approved.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | a + b > 3 4 | → true (caused assertion failure) 5 | 6 | a + b → 7 7 | a → 2 8 | b → 5 9 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.Binary_expressions_with_nesting.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | a < b && (c > -a || c > b) && b < 10 4 | → true (caused assertion failure) 5 | 6 | a < b && (c > -a || c > b) → true 7 | a < b → true 8 | a → 2 9 | b → 5 10 | c > -a || c > b → true 11 | c > -a → true 12 | c → 3.45 13 | -a → -2 14 | c > b → false 15 | b < 10 → true 16 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.DealsOkWithEnumerablesOfAnonymousObjects.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | new[] { foo, foo } 4 | → new[] { 5 | new { 6 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##3 ... 7 | A_short_string = "short", 8 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... ... 9 | }, 10 | new { 11 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##3 ... 12 | A_short_string = "short", 13 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... ... 14 | }, 15 | } (caused assertion failure) 16 | 17 | foo 18 | → new { 19 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36 ..., 20 | A_short_string = "short", 21 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... }, 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.DealsOkWithLongEnumerables.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) == 3.5.ToString(CultureInfo.InvariantCulture) 4 | → false (caused assertion failure) 5 | 6 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) 7 | → "3" 8 | 9 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"] 10 | → 3 11 | 12 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i) 13 | → new Dictionary { 14 | ["n0"] = 0, 15 | ["n1"] = 1, 16 | ["n2"] = 2, 17 | ["n3"] = 3, 18 | ["n4"] = 4, 19 | ["n5"] = 5, 20 | ["n6"] = 6, 21 | ["n7"] = 7, 22 | ["n8"] = 8, 23 | ["n9"] = 9, 24 | ["n10"] = 10, 25 | ["n11"] = 11, 26 | ["n12"] = 12, 27 | ["n13"] = 13, 28 | ["n14"] = 14, 29 | ["n15"] = 15, 30 | ["n16"] = 16, 31 | ["n17"] = 17, 32 | ["n18"] = 18, 33 | ["n19"] = 19, 34 | ["n20"] = 20, 35 | ["n21"] = 21, 36 | ["n22"] = 22, 37 | ["n23"] = 23, 38 | ["n24"] = 24, 39 | ["n25"] = 25, 40 | ["n26"] = 26, 41 | ["n27"] = 27, 42 | ["n28"] = 28, 43 | ["n29"] = 29, 44 | ... 45 | } 46 | 47 | Enumerable.Range(0, 1000) 48 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 49 | 50 | 3.5.ToString(CultureInfo.InvariantCulture) 51 | → "3.5" 52 | 53 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.DealsOkWithLongStrings.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | string.Join("##", Enumerable.Range(0, 100)) + "suffix" 4 | → "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... (caused assertion failure) 5 | 6 | string.Join("##", Enumerable.Range(0, 100)) 7 | → "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 8 | 9 | Enumerable.Range(0, 100) 10 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 11 | 12 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.DealsOkWithObjectsContainingLongMultilineStrings.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | new { A_wall_of_text = wallOfText, A_short_string = "short", A_long_enumerable = Enumerable.Range(0, 1000) } 4 | → @"line 0: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 1 ... 5 | line 1: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 6 | line 2: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 7 | line 3: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 8 | line 4: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 9 | line 5: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 10 | line 6: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 11 | line 7: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 12 | line 8: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 13 | line 9: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 14 | line 10: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 15 | line 11: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 16 | line 12: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 17 | line 13: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 18 | line 14: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 19 | line 15: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 20 | line 16: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 21 | line 17: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 22 | line 18: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 23 | line 19: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 24 | line 20: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 25 | line 21: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 26 | line 22: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 27 | line 23: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 28 | line 24: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 29 | line 25: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 30 | line 26: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 31 | line 27: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 32 | line 28: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 33 | line 29: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 34 | ... (caused assertion failure) 35 | 36 | wallOfText 37 | → @"line 0: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 1 ... 38 | line 1: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 39 | line 2: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 40 | line 3: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 41 | line 4: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 42 | line 5: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 43 | line 6: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 44 | line 7: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 45 | line 8: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 46 | line 9: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 47 | line 10: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 48 | line 11: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 49 | line 12: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 50 | line 13: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 51 | line 14: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 52 | line 15: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 53 | line 16: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 54 | line 17: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 55 | line 18: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 56 | line 19: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 57 | line 20: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 58 | line 21: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 59 | line 22: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 60 | line 23: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 61 | line 24: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 62 | line 25: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 63 | line 26: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 64 | line 27: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 65 | line 28: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 66 | line 29: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 67 | ... 68 | 69 | Enumerable.Range(0, 1000) 70 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 71 | 72 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.DealsOkWithObjectsContainingLongStrings.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | new { A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", A_short_string = "short", A_long_enumerable = Enumerable.Range(0, 1000) } 4 | → "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... (caused assertion failure) 5 | 6 | string.Join("##", Enumerable.Range(0, 100)) + "suffix" 7 | → "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 8 | 9 | string.Join("##", Enumerable.Range(0, 100)) 10 | → "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 11 | 12 | Enumerable.Range(0, 100) 13 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 14 | 15 | Enumerable.Range(0, 1000) 16 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 17 | 18 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.MessyStructureElidesNeatly.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] || false 4 | → false (caused assertion failure) 5 | 6 | hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] 7 | → false 8 | 9 | hmm[1] == hmm[2] 10 | → false 11 | 12 | hmm[1] 13 | → '2' 14 | 15 | hmm 16 | → "1234567890" 17 | 18 | hmm[2] 19 | → '3' 20 | 21 | hmm[4] == hmm[int.Parse(hmm[8].ToString())] 22 | → false 23 | 24 | hmm[4] 25 | → '5' 26 | 27 | hmm[int.Parse(hmm[8].ToString())] 28 | → '0' 29 | 30 | int.Parse(hmm[8].ToString()) 31 | → 9 32 | 33 | hmm[8].ToString() 34 | → "9" 35 | 36 | hmm[8] 37 | → '9' 38 | 39 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.MethodCallsAndArrayLiterals.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average() 4 | → false (caused assertion failure) 5 | 6 | Math.Max(a, b) 7 | → 5 8 | 9 | a 10 | → 2 11 | 12 | b 13 | → 5 14 | 15 | new[] { 3, 8, 13, 4 }.Average() 16 | → 7.0 17 | 18 | new[] { 3, 8, 13, 4 } 19 | → new[] { 3, 8, 13, 4 } 20 | 21 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.NestedArrayAccess.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | nums[a + b] < 7 4 | → false (caused assertion failure) 5 | 6 | nums[a + b] → 17 7 | nums → new[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } 8 | a + b → 7 9 | a → 2 10 | b → 5 11 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.NestedArrayAccessWithOuterAnd.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | a < b && nums[a + b] < 7 && b < 10 4 | → false (caused assertion failure) 5 | 6 | a < b && nums[a + b] < 7 → false 7 | a < b → true 8 | a → 2 9 | b → 5 10 | nums[a + b] < 7 → false 11 | nums[a + b] → 17 12 | nums → new[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } 13 | a + b → 7 14 | b < 10 → true 15 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.NodesThatAreUndescribableAreNotDescribed.approved.txt: -------------------------------------------------------------------------------- 1 | assertion failed 2 | 3 | list.Select(e => e + 1).Count() == 5 4 | → false (caused assertion failure) 5 | 6 | list.Select(e => e + 1).Count() 7 | → 6 8 | 9 | list.Select(e => e + 1) 10 | → { 2, 3, 4, 4, 3, 2 } 11 | 12 | list 13 | → { 1, 2, 3, 3, 2, 1 } 14 | 15 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/SubExpressionPerLineCodeAnnotatorTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class SubExpressionPerLineCodeAnnotatorTest 4 | { 5 | static readonly ExpressionToCodeConfiguration config = 6 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithAnnotator(CodeAnnotators.SubExpressionPerLineCodeAnnotator); 7 | 8 | static readonly IAnnotatedToCode annotator = config.GetAnnotatedToCode(); 9 | 10 | static string AnnotateAsAssertion(Expression> expr) => annotator.AnnotatedToCode(expr.Body, "assertion failed", true); 11 | 12 | [Fact] 13 | public void A_plus_B_approved() 14 | { 15 | var a = 2; 16 | var b = 5; 17 | ApprovalTest.Verify(AnnotateAsAssertion(() => a + b > 3)); 18 | } 19 | 20 | [Fact] 21 | public void Binary_expressions_with_nesting() 22 | { 23 | var a = 2; 24 | var b = 5; 25 | var c = 3.45; 26 | ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && (c > -a || c > b) && b < 10)); 27 | } 28 | 29 | [Fact] 30 | public void DealsOkWithLongEnumerables() 31 | => ApprovalTest.Verify( 32 | AnnotateAsAssertion( 33 | () => Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) == 3.5.ToString(CultureInfo.InvariantCulture) 34 | )); 35 | 36 | [Fact] 37 | public void DealsOkWithLongStrings() 38 | => ApprovalTest.Verify( 39 | AnnotateAsAssertion( 40 | () => string.Join("##", Enumerable.Range(0, 100)) + "suffix" 41 | )); 42 | 43 | [Fact] 44 | public void DealsOkWithObjectsContainingLongStrings() 45 | => ApprovalTest.Verify( 46 | AnnotateAsAssertion( 47 | () => new { 48 | A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", 49 | A_short_string = "short", 50 | A_long_enumerable = Enumerable.Range(0, 1000) 51 | } 52 | )); 53 | 54 | [Fact] 55 | public void DealsOkWithEnumerablesOfAnonymousObjects() 56 | { 57 | var foo = new { 58 | A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", 59 | A_short_string = "short", 60 | A_long_enumerable = Enumerable.Range(0, 1000) 61 | }; 62 | ApprovalTest.Verify( 63 | AnnotateAsAssertion( 64 | () => new[] { 65 | foo, 66 | foo, 67 | })); 68 | } 69 | 70 | [Fact] 71 | public void DealsOkWithObjectsContainingLongMultilineStrings() 72 | { 73 | var wallOfText = 74 | string.Join( 75 | "", 76 | Enumerable.Range(0, 100) 77 | .Select( 78 | line => 79 | $"line {line}:".PadRight(10) 80 | + string.Join( 81 | "", 82 | Enumerable.Range(2, 20).Select(n => $"{n * 10,9};") 83 | ) + "\n" 84 | ) 85 | ); 86 | 87 | ApprovalTest.Verify( 88 | AnnotateAsAssertion( 89 | () => new { 90 | A_wall_of_text = wallOfText, 91 | A_short_string = "short", 92 | A_long_enumerable = Enumerable.Range(0, 1000) 93 | } 94 | )); 95 | } 96 | 97 | [Fact] 98 | public void MessyStructureElidesNeatly() 99 | { 100 | var hmm = "1234567890"; 101 | ApprovalTest.Verify( 102 | AnnotateAsAssertion( 103 | // ReSharper disable once RedundantLogicalConditionalExpressionOperand 104 | () => hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] || false 105 | )); 106 | } 107 | 108 | [Fact] 109 | public void MethodCallsAndArrayLiterals() 110 | { 111 | var a = 2; 112 | var b = 5; 113 | ApprovalTest.Verify(AnnotateAsAssertion(() => Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average())); 114 | } 115 | 116 | [Fact] 117 | public void NestedArrayAccess() 118 | { 119 | var a = 2; 120 | var b = 5; 121 | var nums = Enumerable.Range(10, 10).ToArray(); 122 | ApprovalTest.Verify(AnnotateAsAssertion(() => nums[a + b] < 7)); 123 | } 124 | 125 | [Fact] 126 | public void NestedArrayAccessWithOuterAnd() 127 | { 128 | var a = 2; 129 | var b = 5; 130 | var nums = Enumerable.Range(10, 10).ToArray(); 131 | ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && nums[a + b] < 7 && b < 10)); 132 | } 133 | 134 | [Fact] 135 | public void NodesThatAreUndescribableAreNotDescribed() 136 | { 137 | var list = new List { 1, 2, 3, 3, 2, 1 }; 138 | ApprovalTest.Verify(AnnotateAsAssertion(() => list.Select(e => e + 1).Count() == 5)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ToValuedCodeTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public sealed class ToValuedCodeTest 4 | { 5 | [Fact] 6 | public void ToValuedCode_ofNull_fails() 7 | { 8 | ExpressionToCodeLibTest? obj = null; 9 | 10 | _ = Assert.ThrowsAny( 11 | #pragma warning disable CS8602 // Dereference of a possibly null reference. 12 | () => ExpressionToCode.ToValuedCode(() => obj.TheProperty) 13 | #pragma warning restore CS8602 // Dereference of a possibly null reference. 14 | ); 15 | } 16 | 17 | [Fact] 18 | public void ArrayLength() 19 | { 20 | var arr = new[] { 1 }; 21 | var actual = ExpressionToCode.ToValuedCode(() => arr.Length); 22 | Assert.Equal("arr.Length = 1", actual); 23 | } 24 | 25 | [Fact] 26 | public void ThePropertyAccess() 27 | { 28 | var actual = ExpressionToCode.ToValuedCode(() => TheProperty); 29 | Assert.Equal("ToValuedCodeTest.TheProperty = TheValue", actual); 30 | } 31 | 32 | [Fact] 33 | public void TheStringVariable() 34 | { 35 | var theVariable = "theValue"; 36 | var actual = ExpressionToCode.ToValuedCode(() => theVariable); 37 | Assert.Equal("theVariable = theValue", actual); 38 | } 39 | 40 | [Fact] 41 | public void MethodCall() 42 | { 43 | var actual = ExpressionToCode.ToValuedCode(() => TheMethod(1, "2")); 44 | Assert.Equal("TheMethod(1, \"2\") = TheMethod 1 2", actual); 45 | } 46 | 47 | [Fact] 48 | public void GenericMethodCall() 49 | { 50 | var actual = ExpressionToCode.ToValuedCode(() => TheGenericMethod(2)); 51 | Assert.Equal("TheGenericMethod(2) = Return value is 4", actual); 52 | } 53 | 54 | [Fact] 55 | public void ThisIndexedProperty() 56 | { 57 | var actual = ExpressionToCode.ToValuedCode(() => this[1]); 58 | Assert.Equal("this[1] = TheIndexedValue", actual); 59 | } 60 | 61 | [Fact] 62 | public void ThisMethodCall() 63 | { 64 | var code = ExpressionToCode.ToValuedCode(() => ReturnZero()); 65 | 66 | Assert.Equal("ReturnZero() = 0", code); 67 | } 68 | 69 | [Fact] 70 | public void ThisStaticMethodCall() 71 | { 72 | var code = ExpressionToCode.ToValuedCode(() => StaticReturnZero()); 73 | 74 | Assert.Equal("ToValuedCodeTest.StaticReturnZero() = 0", code); 75 | } 76 | 77 | static string TheProperty 78 | => "TheValue"; 79 | 80 | // ReSharper disable once UnusedParameter.Local 81 | string this[int index] 82 | => "TheIndexedValue"; 83 | 84 | static int StaticReturnZero() 85 | => 0; 86 | 87 | // ReSharper disable MemberCanBeMadeStatic.Local 88 | string TheMethod(int parameter1, string parameter2) 89 | => "TheMethod " + parameter1 + " " + parameter2; 90 | 91 | // ReSharper disable once UnusedTypeParameter 92 | string TheGenericMethod(int two) 93 | => "Return value is " + two * two; 94 | 95 | int ReturnZero() 96 | => 0; 97 | } 98 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/TopLevelProgramTest.cs: -------------------------------------------------------------------------------- 1 | using TopLevelProgramExample; 2 | using static TopLevelProgramExample.TopLevelProgramMarker; 3 | 4 | namespace ExpressionToCodeTest; 5 | 6 | public class TopLevelProgramTest 7 | { 8 | [Fact] 9 | public void CanRunTopLevelProgram() 10 | { 11 | LambdaInsideLocalFunction = LambdaToMyVar = LambdaInsideNestedClassMethod = null; 12 | 13 | var topLevelProgram = (Action)Delegate.CreateDelegate(typeof(Action), typeof(TopLevelProgramMarker).Assembly.EntryPoint ?? throw new("Expected non-null return")); 14 | topLevelProgram(new[] { "test" }); 15 | 16 | Assert.Equal("() => myVariable", LambdaToMyVar); 17 | Assert.Equal("() => InnerClass.StaticInt + InstanceInt", LambdaInsideNestedClassMethod); 18 | Assert.Equal("() => inLocalFunction + myVariable.Length - arg - withImplicitType.A.Length * 27", LambdaInsideLocalFunction); 19 | Assert.Equal("LocalFunction", LocalFunctionToString); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.A_plus_B_approved.approved.txt: -------------------------------------------------------------------------------- 1 | a + b > 3 : assertion failed 2 | │ │ │ 3 | │ │ 5 4 | │ 7 5 | 2 6 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.Binary_expressions_with_nesting.approved.txt: -------------------------------------------------------------------------------- 1 | a < b && (c > -a || c > b) && b < 10 : assertion failed 2 | │ │ │  │  │ │ ││  │ │ │ │     │ │ 3 | │ │ │  │  │ │ ││  │ │ │ │     │ true 4 | │ │ │  │  │ │ ││  │ │ │ │     5 5 | │ │ │  │  │ │ ││  │ │ │ 5 6 | │ │ │  │  │ │ ││  │ │ false 7 | │ │ │  │  │ │ ││  │ 3.45 8 | │ │ │  │  │ │ ││  true 9 | │ │ │  │  │ │ │2 10 | │ │ │  │  │ │ -2 11 | │ │ │  │  │ true 12 | │ │ │  │  3.45 13 | │ │ │  true 14 | │ │ 5 15 | │ true 16 | 2 17 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.DealsOkWithEnumerablesOfAnonymousObjects.approved.txt: -------------------------------------------------------------------------------- 1 | new[] { foo, foo } : assertion failed 2 |          │    │ 3 |          │    new { 4 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36 ..., 5 | A_short_string = "short", 6 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... }, 7 | } 8 |          new { 9 | A_long_string = "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36 ..., 10 | A_short_string = "short", 11 | A_long_enumerable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... }, 12 | } 13 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.DealsOkWithLongEnumerables.approved.txt: -------------------------------------------------------------------------------- 1 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) == 3.5.ToString(CultureInfo.InvariantCulture) : assertion failed 2 |              │                  │                   │          │                        │                    │                        │ 3 |              │                  │                   │          │                        │                    │ 4 |              │                  │                   │          │                        │                    "3.5" 5 |              │                  │                   │          │ 6 |              │                  │                   │          "3" 7 |              │                  │                   3 8 |              │                  new Dictionary { 9 | ["n0"] = 0, 10 | ["n1"] = 1, 11 | ["n2"] = 2, 12 | ["n3"] = 3, 13 | ["n4"] = 4, 14 | ["n5"] = 5, 15 | ["n6"] = 6, 16 | ["n7"] = 7, 17 | ["n8"] = 8, 18 | ["n9"] = 9, 19 | ["n10"] = 10, 20 | ["n11"] = 11, 21 | ["n12"] = 12, 22 | ["n13"] = 13, 23 | ["n14"] = 14, 24 | ["n15"] = 15, 25 | ["n16"] = 16, 26 | ["n17"] = 17, 27 | ["n18"] = 18, 28 | ["n19"] = 19, 29 | ["n20"] = 20, 30 | ["n21"] = 21, 31 | ["n22"] = 22, 32 | ["n23"] = 23, 33 | ["n24"] = 24, 34 | ["n25"] = 25, 35 | ["n26"] = 26, 36 | ["n27"] = 27, 37 | ["n28"] = 28, 38 | ["n29"] = 29, 39 | ... 40 | } 41 |              { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 42 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.DealsOkWithLongStrings.approved.txt: -------------------------------------------------------------------------------- 1 | string.Join("##", Enumerable.Range(0, 100)) + "suffix" : assertion failed 2 |          │                     │ 3 |          │                     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 4 |          "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 5 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.DealsOkWithObjectsContainingLongMultilineStrings.approved.txt: -------------------------------------------------------------------------------- 1 | new { A_wall_of_text = wallOfText, A_short_string = "short", A_long_enumerable = Enumerable.Range(0, 1000) } : assertion failed 2 |                             │                                                                 │ 3 |                             │                                                                 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 4 |                             @"line 0: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 1 ... 5 | line 1: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 6 | line 2: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 7 | line 3: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 8 | line 4: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 9 | line 5: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 10 | line 6: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 11 | line 7: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 12 | line 8: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 13 | line 9: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 14 | line 10: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 15 | line 11: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 16 | line 12: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 17 | line 13: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 18 | line 14: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 19 | line 15: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 20 | line 16: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 21 | line 17: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 22 | line 18: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 23 | line 19: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 24 | line 20: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 25 | line 21: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 26 | line 22: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 27 | line 23: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 28 | line 24: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 29 | line 25: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 30 | line 26: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 31 | line 27: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 32 | line 28: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 33 | line 29: 20; 30; 40; 50; 60; 70; 80; 90; 100; 110; 120; 130; 140; 150 ... 34 | ... 35 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.DealsOkWithObjectsContainingLongStrings.approved.txt: -------------------------------------------------------------------------------- 1 | new { A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", A_short_string = "short", A_long_enumerable = Enumerable.Range(0, 1000) } : assertion failed 2 |                                │                     │            │                                                                      │ 3 |                                │                     │            │                                                                      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 4 |                                │                     │            "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 5 |                                │                     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 6 |                                "0##1##2##3##4##5##6##7##8##9##10##11##12##13##14##15##16##17##18##19##20##21##22##23##24##25##26##27##28##29##30##31##32##33##34##35##36##37##38##39# ... 7 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.MessyStructureElidesNeatly.approved.txt: -------------------------------------------------------------------------------- 1 | hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] || false : assertion failed 2 |  │ │    │  │ │    │  │ │    │  │ │      │    │ │       │ 3 |  │ │    │  │ │    │  │ │    │  │ │      │    │ │       "9" 4 |  │ │    │  │ │    │  │ │    │  │ │      │    │ '9' 5 |  │ │    │  │ │    │  │ │    │  │ │      │    "1234567890" 6 |  │ │    │  │ │    │  │ │    │  │ │      9 7 |  │ │    │  │ │    │  │ │    │  │ '0' 8 |  │ │    │  │ │    │  │ │    │  "1234567890" 9 |  │ │    │  │ │    │  │ │    false 10 |  │ │    │  │ │    │  │ '5' 11 |  │ │    │  │ │    │  "1234567890" 12 |  │ │    │  │ │    false 13 |  │ │    │  │ '3' 14 |  │ │    │  "1234567890" 15 |  │ │    false 16 |  │ '2' 17 |  "1234567890" 18 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.MethodCallsAndArrayLiterals.approved.txt: -------------------------------------------------------------------------------- 1 | Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average() : assertion failed 2 |       │  │  │       │                     │ 3 |       │  │  │       │                     7.0 4 |       │  │  │       new[] { 3, 8, 13, 4 } 5 |       │  │  5 6 |       │  2 7 |       5 8 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.NestedArrayAccess.approved.txt: -------------------------------------------------------------------------------- 1 | nums[a + b] < 7 : assertion failed 2 |   │ ││ │ │ 3 |   │ ││ │ 5 4 |   │ ││ 7 5 |   │ │2 6 |   │ 17 7 |   new[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } 8 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.NestedArrayAccessWithOuterAnd.approved.txt: -------------------------------------------------------------------------------- 1 | a < b && nums[a + b] < 7 && b < 10 : assertion failed 2 | │ │ │  │   │ ││ │ │  │      │ │ 3 | │ │ │  │   │ ││ │ │  │      │ true 4 | │ │ │  │   │ ││ │ │  │      5 5 | │ │ │  │   │ ││ │ │  false 6 | │ │ │  │   │ ││ │ 5 7 | │ │ │  │   │ ││ 7 8 | │ │ │  │   │ │2 9 | │ │ │  │   │ 17 10 | │ │ │  │   new[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } 11 | │ │ │  false 12 | │ │ 5 13 | │ true 14 | 2 15 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.NodesThatAreUndescribableAreNotDescribed.approved.txt: -------------------------------------------------------------------------------- 1 | list.Select(e => e + 1).Count() == 5 : assertion failed 2 |   │     │                 │ 3 |   │     │                 6 4 |   │     { 2, 3, 4, 4, 3, 2 } 5 |   { 1, 2, 3, 3, 2, 1 } 6 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueOnStalksAnnotatorTest.cs: -------------------------------------------------------------------------------- 1 | namespace ExpressionToCodeTest; 2 | 3 | public class ValueOnStalksAnnotatorTest 4 | { 5 | static readonly ExpressionToCodeConfiguration config = 6 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithAnnotator(CodeAnnotators.ValuesOnStalksCodeAnnotator); 7 | 8 | static readonly IAnnotatedToCode annotator = config.GetAnnotatedToCode(); 9 | 10 | static string AnnotateAsAssertion(Expression> expr) => annotator.AnnotatedToCode(expr.Body, "assertion failed", true); 11 | 12 | [Fact] 13 | public void A_plus_B_approved() 14 | { 15 | var a = 2; 16 | var b = 5; 17 | ApprovalTest.Verify(AnnotateAsAssertion(() => a + b > 3)); 18 | } 19 | 20 | [Fact] 21 | public void Binary_expressions_with_nesting() 22 | { 23 | var a = 2; 24 | var b = 5; 25 | var c = 3.45; 26 | ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && (c > -a || c > b) && b < 10)); 27 | } 28 | 29 | [Fact] 30 | public void DealsOkWithLongEnumerables() 31 | => ApprovalTest.Verify( 32 | AnnotateAsAssertion( 33 | () => Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString(CultureInfo.InvariantCulture) == 3.5.ToString(CultureInfo.InvariantCulture) 34 | )); 35 | 36 | [Fact] 37 | public void DealsOkWithLongStrings() 38 | => ApprovalTest.Verify( 39 | AnnotateAsAssertion( 40 | () => string.Join("##", Enumerable.Range(0, 100)) + "suffix" 41 | )); 42 | 43 | [Fact] 44 | public void DealsOkWithObjectsContainingLongStrings() 45 | => ApprovalTest.Verify( 46 | AnnotateAsAssertion( 47 | () => new { 48 | A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", 49 | A_short_string = "short", 50 | A_long_enumerable = Enumerable.Range(0, 1000) 51 | } 52 | )); 53 | 54 | [Fact] 55 | public void DealsOkWithEnumerablesOfAnonymousObjects() 56 | { 57 | var foo = new { 58 | A_long_string = string.Join("##", Enumerable.Range(0, 100)) + "suffix", 59 | A_short_string = "short", 60 | A_long_enumerable = Enumerable.Range(0, 1000) 61 | }; 62 | ApprovalTest.Verify( 63 | AnnotateAsAssertion( 64 | () => new[] { 65 | foo, 66 | foo, 67 | })); 68 | } 69 | 70 | [Fact] 71 | public void DealsOkWithObjectsContainingLongMultilineStrings() 72 | { 73 | var wallOfText = 74 | string.Join( 75 | "", 76 | Enumerable.Range(0, 100) 77 | .Select( 78 | line => 79 | $"line {line}:".PadRight(10) 80 | + string.Join( 81 | "", 82 | Enumerable.Range(2, 20).Select(n => $"{n * 10,9};") 83 | ) + "\n" 84 | ) 85 | ); 86 | 87 | ApprovalTest.Verify( 88 | AnnotateAsAssertion( 89 | () => new { 90 | A_wall_of_text = wallOfText, 91 | A_short_string = "short", 92 | A_long_enumerable = Enumerable.Range(0, 1000) 93 | } 94 | )); 95 | } 96 | 97 | [Fact] 98 | public void MessyStructureElidesNeatly() 99 | { 100 | var hmm = "1234567890"; 101 | ApprovalTest.Verify( 102 | AnnotateAsAssertion( 103 | // ReSharper disable once RedundantLogicalConditionalExpressionOperand 104 | () => hmm[1] == hmm[2] || hmm[4] == hmm[int.Parse(hmm[8].ToString())] || false 105 | )); 106 | } 107 | 108 | [Fact] 109 | public void MethodCallsAndArrayLiterals() 110 | { 111 | var a = 2; 112 | var b = 5; 113 | ApprovalTest.Verify(AnnotateAsAssertion(() => Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average())); 114 | } 115 | 116 | [Fact] 117 | public void NestedArrayAccess() 118 | { 119 | var a = 2; 120 | var b = 5; 121 | var nums = Enumerable.Range(10, 10).ToArray(); 122 | ApprovalTest.Verify(AnnotateAsAssertion(() => nums[a + b] < 7)); 123 | } 124 | 125 | [Fact] 126 | public void NestedArrayAccessWithOuterAnd() 127 | { 128 | var a = 2; 129 | var b = 5; 130 | var nums = Enumerable.Range(10, 10).ToArray(); 131 | ApprovalTest.Verify(AnnotateAsAssertion(() => a < b && nums[a + b] < 7 && b < 10)); 132 | } 133 | 134 | [Fact] 135 | public void NodesThatAreUndescribableAreNotDescribed() 136 | { 137 | var list = new List { 1, 2, 3, 3, 2, 1 }; 138 | ApprovalTest.Verify(AnnotateAsAssertion(() => list.Select(e => e + 1).Count() == 5)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /ExpressionToCodeTest/ValueTupleTests.cs: -------------------------------------------------------------------------------- 1 | using FastExpressionCompiler; 2 | 3 | namespace ExpressionToCodeTest; 4 | 5 | public readonly struct MyValueTuple : IEquatable> 6 | { 7 | public readonly T1 v1; 8 | public readonly T2 v2; 9 | 10 | public MyValueTuple((T1 v1, T2 v2) tuple) 11 | => (v1, v2) = tuple; 12 | 13 | public bool Equals(MyValueTuple other) 14 | => Equals(v1, other.v1) && Equals(v2, other.v2); 15 | } 16 | 17 | public class ValueTupleTests 18 | { 19 | static MyValueTuple ToMyValueTuple((T1, T2) tuple) 20 | => new MyValueTuple(tuple); 21 | 22 | [Fact] 23 | public void ExpressionWithValueTupleEqualsCanCompile() 24 | { 25 | //was a test for https://github.com/dotnet/roslyn/issues/27322 26 | var tupleA = (1, 3); 27 | var tupleB = (1, "123".Length); 28 | 29 | Expression> ok1 = () => tupleA.Item1; 30 | Expression> ok2 = () => tupleA.GetHashCode(); 31 | Expression>> ok3 = () => tupleA.ToTuple(); 32 | Expression> ok4 = () => Equals(tupleA, tupleB); 33 | Expression> ok5 = () => Comparer<(int, int)>.Default.Compare(tupleA, tupleB); 34 | _ = ok1.Compile()(); 35 | _ = ok2.Compile()(); 36 | _ = ok3.Compile()(); 37 | _ = ok4.Compile()(); 38 | _ = ok5.Compile()(); 39 | 40 | var myTupleA = ToMyValueTuple(tupleA); 41 | var myTupleB = ToMyValueTuple(tupleB); 42 | Expression> ok6 = () => myTupleA.Equals(myTupleB); 43 | Expression> ok7 = () => tupleA.ToTuple().Equals(tupleB.ToTuple()); 44 | Expression> ok8 = () => ToMyValueTuple(tupleA).Equals(ToMyValueTuple(tupleB)); 45 | _ = ok6.Compile()(); 46 | _ = ok7.Compile()(); 47 | _ = ok8.Compile()(); 48 | 49 | // ReSharper disable once UnusedVariable 50 | Expression> err1 = () => tupleA.Equals(tupleB); //crash 51 | // ReSharper disable once UnusedVariable 52 | Expression> err2 = () => tupleA.CompareTo(tupleB); //crash 53 | } 54 | 55 | [Fact] 56 | public void FastExpressionCompileValueTupleEqualsWorks() 57 | { 58 | //was a test for https://github.com/dotnet/roslyn/issues/27322 59 | var tuple = (1, 3); 60 | var tuple2 = (1, "123".Length); 61 | var expr = ExpressionCompiler.CompileFast(() => tuple.Equals(tuple2)); 62 | Assert.True(expr()); 63 | } 64 | 65 | [Fact] 66 | public void AssertingOnValueTupleEqualsWorks() 67 | { 68 | //was a test for https://github.com/dotnet/roslyn/issues/27322 69 | var tuple = (1, 3); 70 | var tuple2 = (1, "123".Length); 71 | Expression> expr = () => tuple.Equals(tuple2); 72 | Assert.True(expr.Compile()()); 73 | PAssert.That(() => tuple.Equals(tuple2)); 74 | } 75 | 76 | [Fact] 77 | public void ToCSharpFriendlyTypeNameSupportsTuples() 78 | { 79 | var actual = (1, "2", new[] { 1, 2, 3 }); 80 | Assert.Equal("(int, string, int[])", actual.GetType().ToCSharpFriendlyTypeName()); 81 | } 82 | 83 | [Fact] 84 | public void ToCSharpFriendlyTypeNameSupportsLongTuples() 85 | { 86 | var actual = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 87 | Assert.Equal("(int, int, int, int, int, int, int, int, int, int)", actual.GetType().ToCSharpFriendlyTypeName()); 88 | } 89 | 90 | [Fact] 91 | public void ToCSharpFriendlyTypeNameSupportsNestedTuples() 92 | { 93 | var actual = (1, 2, ((3, 4), 5), 6, 7, 8); 94 | Assert.Equal("(int, int, ((int, int), int), int, int, int)", actual.GetType().ToCSharpFriendlyTypeName()); 95 | } 96 | 97 | [Fact] 98 | public void ToCSharpFriendlyTypeNameSupportsNestedNullableTuples() 99 | { 100 | var actual = default((int, int, ((int, int), int), (int, int)?, int, int)); 101 | Assert.Equal("(int, int, ((int, int), int), (int, int)?, int, int)", actual.GetType().ToCSharpFriendlyTypeName()); 102 | } 103 | 104 | [Fact] 105 | public void ToCSharpFriendlyTypeNameSupportsTrailingNestedTuples() 106 | { 107 | var actual = default((int, int, ((int, int), int))); 108 | Assert.Equal("(int, int, ((int, int), int))", actual.GetType().ToCSharpFriendlyTypeName()); 109 | } 110 | 111 | [Fact] 112 | public void ToCSharpFriendlyTypeNameSupportsTrailingNestedTuplesAtPosition8() 113 | { 114 | var actual = default((int, int, int, int, int, int, int, (int, int))); 115 | var alt = default((int, int, int, int, int, int, int, int, int)); 116 | Assert.False(actual.GetType() == alt.GetType()); //non-obvious compiler-guarranteed precondition 117 | Assert.Equal("(int, int, int, int, int, int, int, (int, int))", actual.GetType().ToCSharpFriendlyTypeName()); 118 | } 119 | 120 | [Fact] 121 | public void ComplexObjectToPseudoCodeSupportsTuples() 122 | { 123 | var actual = (1, "2", new[] { 1, 2, 3 }); 124 | Assert.Equal("(1, \"2\", new[] { 1, 2, 3 })", ObjectToCode.ComplexObjectToPseudoCode(actual)); 125 | } 126 | 127 | [Fact] 128 | public void ComplexObjectToPseudoCodeSupportsComplexTuples() 129 | { 130 | var actual = (1, ("2", new[] { "2b" }), new[] { 3 }, 4, default(string), 6, 7, 8, 9); 131 | Assert.Equal("(1, (\"2\", new[] { \"2b\" }), new[] { 3 }, 4, null, 6, 7, 8, 9)", ObjectToCode.ComplexObjectToPseudoCode(actual)); 132 | } 133 | 134 | [Fact] 135 | public void ComplexObjectToPseudoCodeSupportsTuplesWithTrailingTuples() 136 | { 137 | var actual = (1, 2, 3, 4, 5, 6, 7, (8, 9, 10)); 138 | Assert.Equal("(1, 2, 3, 4, 5, 6, 7, (8, 9, 10))", ObjectToCode.ComplexObjectToPseudoCode(actual)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExpressionToCode 2 | ================ 3 | ExpressionToCode generates valid, readable C# from an Expression Tree. (nuget: [ExpressionToCodeLib](http://nuget.org/packages/ExpressionToCodeLib/)) 4 | ------ 5 | 6 | 7 | An example: 8 | 9 | ```C# 10 | ExpressionToCode.ToCode( 11 | () => new[] { 1.0, 2.01, 3.5 }.SequenceEqual(new[] { 1.0, 2.01, 3.5 }) 12 | ) 13 | == "() => new[] { 1.0, 2.01, 3.5 }.SequenceEqual(new[] { 1.0, 2.01, 3.5 })" 14 | ``` 15 | 16 | ExpressionToCode also provides something like Groovy's [Power Assert](http://dontmindthelanguage.wordpress.com/2009/12/11/groovy-1-7-power-assert/) which includes the code of the failing assertion's expression and the values of its subexpressions. This functionality is particularly useful in a unit testing framework such as [NUnit](http://www.nunit.org/) or [xUnit.NET](http://xunit.github.io/). When you execute the following (failing) assertion: 17 | 18 | ```C# 19 | PAssert.That(() => Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString() == (3.5).ToString()); 20 | ``` 21 | 22 | The assertion fails with the following message: 23 | 24 | ``` 25 | assertion failed 26 | 27 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString() == 3.5.ToString() 28 | → false (caused assertion failure) 29 | 30 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"].ToString() 31 | → "3" 32 | 33 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i)["n3"] 34 | → 3 35 | 36 | Enumerable.Range(0, 1000).ToDictionary(i => "n" + i) 37 | → new Dictionary { 38 | ["n0"] = 0, 39 | ["n1"] = 1, 40 | ["n2"] = 2, 41 | ["n3"] = 3, 42 | ["n4"] = 4, 43 | ["n5"] = 5, 44 | ["n6"] = 6, 45 | ["n7"] = 7, 46 | ["n8"] = 8, 47 | ["n9"] = 9, 48 | ["n10"] = 10, 49 | ["n11"] = 11, 50 | ["n12"] = 12, 51 | ["n13"] = 13, 52 | ["n14"] = 14, 53 | ["n15"] = 15, 54 | ["n16"] = 16, 55 | ["n17"] = 17, 56 | ["n18"] = 18, 57 | ["n19"] = 19, 58 | ["n20"] = 20, 59 | ["n21"] = 21, 60 | ["n22"] = 22, 61 | ["n23"] = 23, 62 | ["n24"] = 24, 63 | ["n25"] = 25, 64 | ["n26"] = 26, 65 | ["n27"] = 27, 66 | ["n28"] = 28, 67 | ["n29"] = 29, 68 | ... 69 | } 70 | 71 | Enumerable.Range(0, 1000) 72 | → { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, ... } 73 | 74 | 3.5.ToString() 75 | → "3.5" 76 | 77 | ``` 78 | 79 | ExpressionToCode's output is configurable in various ways. For expressions with small values, a values-on-stalks rendering might instead be used: 80 | ```C# 81 | var a = 2; 82 | var b = 5; 83 | ExpressionToCodeConfiguration.DefaultAssertionConfiguration.WithAnnotator(CodeAnnotators.ValuesOnStalksCodeAnnotator) 84 | .Assert(() => Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average() ); 85 | ``` 86 | 87 | ``` 88 | Math.Max(a, b) > new[] { 3, 8, 13, 4 }.Average() : assertion failed 89 |       │  │  │       │                     │ 90 |       │  │  │       │                     7.0 91 |       │  │  │       new[] { 3, 8, 13, 4 } 92 |       │  │  5 93 |       │  2 94 |       5 95 | ``` 96 | 97 | Note that the default configuration for asserts (i.e. `PAssert.That`) limits the length of sequences and strings; the default configuration of code-generation does not. 98 | 99 | ExpressionToCode was inspired by [Power Assert.NET](https://github.com/PowerAssert/PowerAssert.Net). It differs from PowerAssert.NET by supporting a larger portion of the lambda syntax and that the generated C# is more frequently valid; the aim is to generate valid C# for *all* expression trees created from lambda's. Currently supported: 100 | 101 | Expression tree support 102 | --- 103 | 104 | * Supports static field and property access 105 | * Supports more operators, e.g. logical and bitwise negation 106 | * Recognizes C# indexer use (e.g. `dict["mykey"]==3`), in addition to special cases for array indexers and string indexers 107 | * Adds parentheses where required by operator precedence and associativity (e.g. `() => x - (a - b) + x * (a + b)` is correctly regenerated) 108 | * Generates valid numeric and other constant literals including escapes and suffixes where required (e.g. `1m + (decimal)Math.Sqrt(1.41)`) 109 | * Supports C# syntactic sugar for object initializers, object member initializers, list initializers, extension methods, anonymous types (issues [#12](/../../issues/12), [#3](/../../issues/3)), etc 110 | * Uses the same spacing rules Visual Studio does by default 111 | * Supports nested Lambdas 112 | * Expands generic type instances and nullable types into normal C# (e.g. `Func` and `int?`) 113 | * Recognizes references to `this` and omits the keyword where possible ([#5](/../../issues/5)) 114 | * Recognizes closed-over variables and prints something plausible, rather than the crypic compiler-generated names. 115 | * Omits most implicit casts (e.g. `object.Equals(3, 4)` instead of `object.Equals((object)3, (object)4)`) - user defined implicit cast operators are not elided ([#4](/../../issues/4)) 116 | * Detects when type parameters to methods are superfluous ([#13](/../../issues/13)). 117 | 118 | **Not implemented (yet?):** 119 | * Use LINQ query syntax where possible - issue [#6](/../../issues/6). 120 | * Explicitly cast otherwise inferable lambda when required due to ambiguous overloads - issue [#14](/../../issues/14). 121 | * Warn when `==` differs from `.Equals` or `.SequenceEquals`, as Power Assert.NET does (issue [#2](/../../issues/2)). 122 | * See all [open issues](https://github.com/EamonNerbonne/ExpressionToCode/issues). 123 | 124 | `ExpressionToCode` API 125 | ----- 126 | 127 | All classes live in the `ExpressionToCodeLib` namespace. 128 | 129 | These are: 130 | * `PAssert` for making assertions in NUnit tests and elsewhere. 131 | * `PAssert.That` and `PAssert.IsTrue` are identical; both test the provided boolean expression and print a readable error message on failure 132 | * `ExpressionToCode` Renders a System.Linq.Expressions.Expression object to source code. 133 | * `ExpressionToCode.ToCode` (several overloads) simply renders the expression as source code. 134 | * `ExpressionToCode.AnnotatedToCode` (several overloads) renders the expression as source code, then annotates all subexpressions which are computable with their value using the stalk-like rendering as shown on the Project Home page. 135 | 136 | Two public helper classes exist: 137 | 138 | * `PAssertFailedException` thrown on assertion failure. 139 | * `ObjectToCode` Renders .NET objects to code; a helper class. 140 | * `ObjectToCode.PlainObjectToCode` renders simple objects that can be parsed by the C# compiler. This includes strings, chars, decimals, floats, doubles, all the integer types, booleans, enums, nulls, and default struct values. 141 | * `ObjectToCode.ComplexObjectToPseudoCode` renders as best it can anything thrown at it; but the resultant rendering is not necessarily compilable. This is used to display the values of subexpressions. 142 | 143 | A complete listing of the public api is [here](ExpressionToCodeTest/ApiStabilityTest.PublicApi.approved.txt) 144 | 145 | Supported platforms 146 | 147 | --- 148 | 149 | Requires .NET 4.5.2 or .net standard 1.6 (previous versions support older platforms) 150 | 151 | --- 152 | 153 | If you have any questions, you can contact me via github or mail eamon at nerbonne dot org. 154 | 155 | See the documentation above, then download from or [import using NuGet](http://nuget.org/packages/ExpressionToCodeLib/), or just checkout the source (license: Apache 2.0 or the MIT license, at your option)! 156 | -------------------------------------------------------------------------------- /TopLevelProgramExample/TopLevelProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using ExpressionToCodeLib; 4 | using TopLevelProgramExample; 5 | 6 | const int SomeConst = 27; 7 | var myVariable = "implicitly closed over"; 8 | var withImplicitType = new { 9 | A = "ImplicitTypeMember", 10 | }; 11 | Console.WriteLine(TopLevelProgramMarker.LambdaToMyVar = ExpressionToCode.ToCode(() => myVariable)); 12 | 13 | Console.WriteLine(TopLevelProgramMarker.LocalFunctionToString = ObjectToCode.ComplexObjectToPseudoCode(((Action)LocalFunction).Method)); 14 | 15 | new InnerClass().DoIt(); 16 | LocalFunction(123); 17 | 18 | void LocalFunction(int arg) 19 | { 20 | var inLocalFunction = 42; 21 | Expression> expression1 = () => inLocalFunction + myVariable.Length - arg - withImplicitType.A.Length * SomeConst; 22 | 23 | Console.WriteLine(TopLevelProgramMarker.LambdaInsideLocalFunction = ExpressionToCode.ToCode(expression1)); 24 | } 25 | 26 | sealed class InnerClass 27 | { 28 | public static int StaticInt = 37; 29 | public int InstanceInt = 12; 30 | 31 | public void DoIt() 32 | => Console.WriteLine(TopLevelProgramMarker.LambdaInsideNestedClassMethod = ExpressionToCode.ToCode(() => StaticInt + InstanceInt)); 33 | } 34 | -------------------------------------------------------------------------------- /TopLevelProgramExample/TopLevelProgramExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net48;net8.0 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TopLevelProgramExample/TopLevelProgramMarker.cs: -------------------------------------------------------------------------------- 1 | namespace TopLevelProgramExample; 2 | 3 | public static class TopLevelProgramMarker 4 | { 5 | public static string LambdaToMyVar; 6 | public static string LambdaInsideLocalFunction; 7 | public static string LambdaInsideNestedClassMethod; 8 | public static string LocalFunctionToString; 9 | } 10 | -------------------------------------------------------------------------------- /how-to-push-to-nuget.txt: -------------------------------------------------------------------------------- 1 | to publish, run the following commands in the library directory: 2 | 3 | dotnet clean 4 | dotnet pack -c release 5 | dotnet nuget push -s https://api.nuget.org/v3/index.json .\bin\Release\..nupkg 6 | 7 | You must also have configured your nuget api-key, e.g. via NuGet.exe setApiKey . The api key is saved in your profile, so you need to do that only once. --------------------------------------------------------------------------------