├── .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