From 37265caeb8b72a4551b9609cd0ea10b0a63b0ab3 Mon Sep 17 00:00:00 2001 From: stakx Date: Fri, 9 Aug 2019 02:24:29 +0200 Subject: [PATCH] Merge cleaned-up `ExpressionStringBuilder` into `StringBuilderExtensions` (#882) * Convert `ExpressionStringBuilder` to extension methods * Chain `StringBuilder` operations * General-purpose `AppendCommaSeparated` method reduces code duplication * Inline a few one-line methods * Simplify enclosed comma-separated lists code pattern * Refactor more existing code to use `AppendCommaSeparated` * Use more regular method names * Merge into `StringBuilderExtensions` ... after ensuring that the (previous) `ExpressionStringBuilder` code file only contains `AppendExpression` methods at the top level (every- thing else is turned into local functions). * Inline event add/remove formatting code --- src/Moq/ExpressionExtensions.cs | 4 +- src/Moq/ExpressionStringBuilder.cs | 613 ------------------ src/Moq/Extensions.cs | 2 +- ...tringBuilderExtensions.AppendExpression.cs | 472 ++++++++++++++ src/Moq/StringBuilderExtensions.cs | 67 +- .../ExpressionStringBuilderFixture.cs | 52 -- tests/Moq.Tests/ExtensionsFixture.cs | 31 + .../StringBuilderExtensionsFixture.cs | 56 +- 8 files changed, 568 insertions(+), 729 deletions(-) delete mode 100644 src/Moq/ExpressionStringBuilder.cs create mode 100644 src/Moq/StringBuilderExtensions.AppendExpression.cs delete mode 100644 tests/Moq.Tests/ExpressionStringBuilderFixture.cs diff --git a/src/Moq/ExpressionExtensions.cs b/src/Moq/ExpressionExtensions.cs index 4332d1189..270c1d4d5 100644 --- a/src/Moq/ExpressionExtensions.cs +++ b/src/Moq/ExpressionExtensions.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; +using System.Text; using Moq.Properties; using Moq.Protected; @@ -419,7 +419,7 @@ private static bool PartialMatcherAwareEval_ShouldEvaluate(Expression expression /// public static string ToStringFixed(this Expression expression) { - return new ExpressionStringBuilder().Append(expression).ToString(); + return new StringBuilder().AppendExpression(expression).ToString(); } } } diff --git a/src/Moq/ExpressionStringBuilder.cs b/src/Moq/ExpressionStringBuilder.cs deleted file mode 100644 index 1fe34f545..000000000 --- a/src/Moq/ExpressionStringBuilder.cs +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD. -// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; - -using Moq.Properties; - -namespace Moq -{ - /// - /// The intention of is to create a more readable - /// string representation for the failure message. - /// - internal class ExpressionStringBuilder - { - private StringBuilder builder; - - public ExpressionStringBuilder() - { - this.builder = new StringBuilder(); - } - - public ExpressionStringBuilder Append(Expression expression) - { - this.ToString(expression); - return this; - } - - public override string ToString() - { - return this.builder.ToString(); - } - - private void ToString(Expression exp) - { - if (exp == null) - { - builder.Append("null"); - return; - } - switch (exp.NodeType) - { - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - ToStringUnary((UnaryExpression)exp); - return; - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.AddAssign: - case ExpressionType.Assign: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.SubtractAssign: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - ToStringBinary((BinaryExpression)exp); - return; - case ExpressionType.TypeIs: - ToStringTypeIs((TypeBinaryExpression)exp); - return; - case ExpressionType.Conditional: - ToStringConditional((ConditionalExpression)exp); - return; - case ExpressionType.Constant: - ToStringConstant((ConstantExpression)exp); - return; - case ExpressionType.Parameter: - ToStringParameter((ParameterExpression)exp); - return; - case ExpressionType.MemberAccess: - ToStringMemberAccess((MemberExpression)exp); - return; - case ExpressionType.Call: - ToStringMethodCall((MethodCallExpression)exp); - return; - case ExpressionType.Index: - ToStringIndex((IndexExpression)exp); - return; - case ExpressionType.Lambda: - ToStringLambda((LambdaExpression)exp); - return; - case ExpressionType.New: - ToStringNew((NewExpression)exp); - return; - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - ToStringNewArray((NewArrayExpression)exp); - return; - case ExpressionType.Invoke: - ToStringInvocation((InvocationExpression)exp); - return; - case ExpressionType.MemberInit: - ToStringMemberInit((MemberInitExpression)exp); - return; - case ExpressionType.ListInit: - ToStringListInit((ListInitExpression)exp); - return; - case ExpressionType.Extension: - if (exp is MatchExpression me) - { - ToStringMatch(me); - return; - } - goto default; - default: - throw new Exception(string.Format(Resources.UnhandledExpressionType, exp.NodeType)); - } - } - - private void ToStringBinding(MemberBinding binding) - { - switch (binding.BindingType) - { - case MemberBindingType.Assignment: - ToStringMemberAssignment((MemberAssignment)binding); - return; - case MemberBindingType.MemberBinding: - ToStringMemberMemberBinding((MemberMemberBinding)binding); - return; - case MemberBindingType.ListBinding: - ToStringMemberListBinding((MemberListBinding)binding); - return; - default: - throw new Exception(string.Format(Resources.UnhandledBindingType, binding.BindingType)); - } - } - - private void ToStringElementInitializer(ElementInit initializer) - { - builder.Append("{ "); - ToStringExpressionList(initializer.Arguments); - builder.Append(" }"); - return; - } - - private void ToStringUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - builder.Append('(').AppendNameOf(u.Type).Append(')'); - ToString(u.Operand); - return; - - case ExpressionType.ArrayLength: - ToString(u.Operand); - builder.Append(".Length"); - return; - - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - builder.Append('-'); - ToString(u.Operand); - return; - - case ExpressionType.Not: - builder.Append("!("); - ToString(u.Operand); - builder.Append(')'); - return; - - case ExpressionType.Quote: - ToString(u.Operand); - return; - - case ExpressionType.TypeAs: - builder.Append('('); - ToString(u.Operand); - builder.Append(" as "); - builder.AppendNameOf(u.Type); - builder.Append(')'); - return; - } - return; - } - - private void ToStringBinary(BinaryExpression b) - { - if (b.NodeType == ExpressionType.ArrayIndex) - { - ToString(b.Left); - builder.Append('['); - ToString(b.Right); - builder.Append(']'); - } - else - { - string @operator = ToStringOperator(b.NodeType); - if (NeedEncloseInParen(b.Left)) - { - builder.Append('('); - ToString(b.Left); - builder.Append(')'); - } - else - { - ToString(b.Left); - } - builder.Append(' '); - builder.Append(@operator); - builder.Append(' '); - if (NeedEncloseInParen(b.Right)) - { - builder.Append('('); - ToString(b.Right); - builder.Append(')'); - } - else - { - ToString(b.Right); - } - } - } - - private static bool NeedEncloseInParen(Expression operand) - { - return operand.NodeType == ExpressionType.AndAlso || operand.NodeType == ExpressionType.OrElse; - } - - private void ToStringTypeIs(TypeBinaryExpression b) - { - ToString(b.Expression); - return; - } - - private void ToStringConstant(ConstantExpression c) - { - builder.AppendValueOf(c.Value); - } - - private void ToStringConditional(ConditionalExpression c) - { - ToString(c.Test); - builder.Append(" ? "); - ToString(c.IfTrue); - builder.Append(" : "); - ToString(c.IfFalse); - return; - } - - private void ToStringParameter(ParameterExpression p) - { - if (p.Name != null) - { - builder.Append(p.Name); - } - else - { - builder.Append(""); - } - } - - private void ToStringMemberAccess(MemberExpression m) - { - if (m.Expression != null) - { - ToString(m.Expression); - } - else - { - builder.AppendNameOf(m.Member.DeclaringType); - } - builder.Append('.'); - builder.Append(m.Member.Name); - return; - } - - private void ToStringMethodCall(MethodCallExpression node) - { - if (node != null) - { - var paramFrom = 0; - var expression = node.Object; - - if (node.Method.IsExtensionMethod()) - { - paramFrom = 1; - expression = node.Arguments[0]; - } - - if (expression != null) - { - ToString(expression); - } - else // Method is static - { - this.builder.AppendNameOf(node.Method.DeclaringType); - } - - if (node.Method.IsPropertyIndexerGetter()) - { - this.builder.Append('['); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom), ToString); - this.builder.Append(']'); - } - else if (node.Method.IsPropertyIndexerSetter()) - { - this.builder.Append('['); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom).Take(node.Arguments.Count - paramFrom - 1), ToString); - this.builder.Append("] = "); - ToString(node.Arguments.Last()); - } - else if (node.Method.IsPropertyGetter()) - { - this.builder.Append('.').Append(node.Method.Name.Substring(4)); - if (node.Arguments.Count > paramFrom) - { - this.builder.Append('['); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom), ToString); - this.builder.Append(']'); - } - } - else if (node.Method.IsPropertySetter()) - { - this.builder.Append('.').Append(node.Method.Name.Substring(4)).Append(" = "); - ToString(node.Arguments.Last()); - } - else if (node.Method.LooksLikeEventAttach()) - { - this.builder.Append('.') - .AppendNameOfAddEvent(node.Method, includeGenericArgumentList: true); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom), ToString); - } - else if (node.Method.LooksLikeEventDetach()) - { - this.builder.Append('.') - .AppendNameOfRemoveEvent(node.Method, includeGenericArgumentList: true); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom), ToString); - } - else - { - this.builder - .Append('.') - .AppendNameOf(node.Method, includeGenericArgumentList: true) - .Append('('); - AsCommaSeparatedValues(node.Arguments.Skip(paramFrom), ToString); - this.builder.Append(')'); - } - } - } - - private void ToStringIndex(IndexExpression expression) - { - ToString(expression.Object); - this.builder.Append('['); - ToStringExpressionList(expression.Arguments); - this.builder.Append(']'); - } - - private void ToStringExpressionList(IEnumerable original) - { - AsCommaSeparatedValues(original, ToString); - return; - } - - private void ToStringMemberAssignment(MemberAssignment assignment) - { - builder.Append(assignment.Member.Name); - builder.Append("= "); - ToString(assignment.Expression); - return; - } - - private void ToStringMemberMemberBinding(MemberMemberBinding binding) - { - ToStringBindingList(binding.Bindings); - return; - } - - private void ToStringMemberListBinding(MemberListBinding binding) - { - ToStringElementInitializerList(binding.Initializers); - return; - } - - private void ToStringBindingList(IEnumerable original) - { - bool appendComma = false; - foreach (var exp in original) - { - if (appendComma) - { - builder.Append(", "); - } - ToStringBinding(exp); - appendComma = true; - } - return; - } - - private void ToStringElementInitializerList(ReadOnlyCollection original) - { - for (int i = 0, n = original.Count; i < n; i++) - { - ToStringElementInitializer(original[i]); - } - return; - } - - private void ToStringLambda(LambdaExpression lambda) - { - if (lambda.Parameters.Count == 1) - { - ToStringParameter(lambda.Parameters[0]); - } - else - { - builder.Append('('); - AsCommaSeparatedValues(lambda.Parameters, ToStringParameter); - builder.Append(')'); - } - builder.Append(" => "); - ToString(lambda.Body); - return; - } - - private void ToStringNew(NewExpression nex) - { - Type type = (nex.Constructor == null) ? nex.Type : nex.Constructor.DeclaringType; - builder.Append("new "); - builder.AppendNameOf(type); - builder.Append('('); - AsCommaSeparatedValues(nex.Arguments, ToString); - builder.Append(')'); - return; - } - - private void ToStringMemberInit(MemberInitExpression init) - { - ToStringNew(init.NewExpression); - builder.Append(" { "); - ToStringBindingList(init.Bindings); - builder.Append(" }"); - return; - } - - private void ToStringListInit(ListInitExpression init) - { - ToStringNew(init.NewExpression); - builder.Append(" { "); - bool appendComma = false; - foreach (var initializer in init.Initializers) - { - if (appendComma) - { - builder.Append(", "); - } - ToStringElementInitializer(initializer); - appendComma = true; - } - builder.Append(" }"); - return; - } - - private void ToStringNewArray(NewArrayExpression na) - { - switch (na.NodeType) - { - case ExpressionType.NewArrayInit: - builder.Append("new[] { "); - AsCommaSeparatedValues(na.Expressions, ToString); - builder.Append(" }"); - return; - case ExpressionType.NewArrayBounds: - builder.Append("new "); - builder.AppendNameOf(na.Type.GetElementType()); - builder.Append('['); - AsCommaSeparatedValues(na.Expressions, ToString); - builder.Append(']'); - return; - } - } - - private void ToStringMatch(MatchExpression node) - { - ToString(node.Match.RenderExpression); - return; - } - - private void AsCommaSeparatedValues(IEnumerable source, Action toStringAction) where T : Expression - { - bool appendComma = false; - foreach (var exp in source) - { - if (appendComma) - { - builder.Append(", "); - } - toStringAction(exp); - appendComma = true; - } - } - - private void ToStringInvocation(InvocationExpression iv) - { - ToString(iv.Expression); - builder.Append('('); - ToStringExpressionList(iv.Arguments); - builder.Append(')'); - return; - } - - internal static string ToStringOperator(ExpressionType nodeType) - { - switch (nodeType) - { - case ExpressionType.Add: - case ExpressionType.AddChecked: - return "+"; - - case ExpressionType.AddAssign: - return "+="; - - case ExpressionType.Assign: - return "="; - - case ExpressionType.And: - return "&"; - - case ExpressionType.AndAlso: - return "&&"; - - case ExpressionType.Coalesce: - return "??"; - - case ExpressionType.Divide: - return "/"; - - case ExpressionType.Equal: - return "=="; - - case ExpressionType.ExclusiveOr: - return "^"; - - case ExpressionType.GreaterThan: - return ">"; - - case ExpressionType.GreaterThanOrEqual: - return ">="; - - case ExpressionType.LeftShift: - return "<<"; - - case ExpressionType.LessThan: - return "<"; - - case ExpressionType.LessThanOrEqual: - return "<="; - - case ExpressionType.Modulo: - return "%"; - - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - return "*"; - - case ExpressionType.NotEqual: - return "!="; - - case ExpressionType.Or: - return "|"; - - case ExpressionType.OrElse: - return "||"; - - case ExpressionType.Power: - return "^"; - - case ExpressionType.RightShift: - return ">>"; - - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - return "-"; - - case ExpressionType.SubtractAssign: - return "-="; - } - return nodeType.ToString(); - } - } -} diff --git a/src/Moq/Extensions.cs b/src/Moq/Extensions.cs index 13c686b81..8710b89c9 100644 --- a/src/Moq/Extensions.cs +++ b/src/Moq/Extensions.cs @@ -191,7 +191,7 @@ public static bool CompareTo(this TTypes types, TOtherTypes public static string GetParameterTypeList(this MethodInfo method) { - return new StringBuilder().AppendParameterTypeList(method.GetParameters()).ToString(); + return new StringBuilder().AppendCommaSeparated(method.GetParameters(), StringBuilderExtensions.AppendParameterType).ToString(); } public static ParameterTypes GetParameterTypes(this MethodInfo method) diff --git a/src/Moq/StringBuilderExtensions.AppendExpression.cs b/src/Moq/StringBuilderExtensions.AppendExpression.cs new file mode 100644 index 000000000..497539b08 --- /dev/null +++ b/src/Moq/StringBuilderExtensions.AppendExpression.cs @@ -0,0 +1,472 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +using Moq.Properties; + +namespace Moq +{ + // These methods are intended to create more readable string representations for use in failure messages. + partial class StringBuilderExtensions + { + public static StringBuilder AppendExpression(this StringBuilder builder, Expression expression) + { + if (expression == null) + { + return builder.Append("null"); + } + switch (expression.NodeType) + { + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return builder.AppendExpression((UnaryExpression)expression); + + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.AddAssign: + case ExpressionType.Assign: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.SubtractAssign: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + return builder.AppendExpression((BinaryExpression)expression); + + case ExpressionType.TypeIs: + return builder.AppendExpression((TypeBinaryExpression)expression); + + case ExpressionType.Conditional: + return builder.AppendExpression((ConditionalExpression)expression); + + case ExpressionType.Constant: + return builder.AppendValueOf(((ConstantExpression)expression).Value); + + case ExpressionType.Parameter: + return builder.AppendExpression((ParameterExpression)expression); + + case ExpressionType.MemberAccess: + return builder.AppendExpression((MemberExpression)expression); + + case ExpressionType.Call: + return builder.AppendExpression((MethodCallExpression)expression); + + case ExpressionType.Index: + return builder.AppendExpression((IndexExpression)expression); + + case ExpressionType.Lambda: + return builder.AppendExpression((LambdaExpression)expression); + + case ExpressionType.New: + return builder.AppendExpression((NewExpression)expression); + + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + return builder.AppendExpression((NewArrayExpression)expression); + + case ExpressionType.Invoke: + return builder.AppendExpression((InvocationExpression)expression); + + case ExpressionType.MemberInit: + return builder.AppendExpression((MemberInitExpression)expression); + + case ExpressionType.ListInit: + return builder.AppendExpression((ListInitExpression)expression); + + case ExpressionType.Extension: + if (expression is MatchExpression me) + { + return builder.AppendExpression(me); + } + goto default; + + default: + throw new Exception(string.Format(Resources.UnhandledExpressionType, expression.NodeType)); + } + } + + private static StringBuilder AppendElementInit(this StringBuilder builder, ElementInit initializer) + { + return builder.AppendCommaSeparated("{ ", initializer.Arguments, AppendExpression, " }"); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, UnaryExpression expression) + { + switch (expression.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + return builder.Append('(') + .AppendNameOf(expression.Type) + .Append(')') + .AppendExpression(expression.Operand); + + case ExpressionType.ArrayLength: + return builder.AppendExpression(expression.Operand) + .Append(".Length"); + + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + return builder.Append('-') + .AppendExpression(expression.Operand); + + case ExpressionType.Not: + return builder.Append("!(") + .AppendExpression(expression.Operand) + .Append(')'); + + case ExpressionType.Quote: + return builder.AppendExpression(expression.Operand); + + case ExpressionType.TypeAs: + return builder.Append('(') + .AppendExpression(expression.Operand) + .Append(" as ") + .AppendNameOf(expression.Type) + .Append(')'); + } + + return builder; // TODO: check whether this should be unreachable + } + + private static StringBuilder AppendExpression(this StringBuilder builder, BinaryExpression expression) + { + if (expression.NodeType == ExpressionType.ArrayIndex) + { + builder.AppendExpression(expression.Left) + .Append('[') + .AppendExpression(expression.Right) + .Append(']'); + } + else + { + AppendMaybeParenthesized(expression.Left, builder); + builder.Append(' ') + .Append(GetOperator(expression.NodeType)) + .Append(' '); + AppendMaybeParenthesized(expression.Right, builder); + } + + return builder; + + void AppendMaybeParenthesized(Expression operand, StringBuilder b) + { + bool parenthesize = operand.NodeType == ExpressionType.AndAlso || operand.NodeType == ExpressionType.OrElse; + if (parenthesize) + { + b.Append("("); + } + b.AppendExpression(operand); + if (parenthesize) + { + b.Append(")"); + } + } + + string GetOperator(ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.Add: + case ExpressionType.AddChecked: + return "+"; + + case ExpressionType.AddAssign: + return "+="; + + case ExpressionType.Assign: + return "="; + + case ExpressionType.And: + return "&"; + + case ExpressionType.AndAlso: + return "&&"; + + case ExpressionType.Coalesce: + return "??"; + + case ExpressionType.Divide: + return "/"; + + case ExpressionType.Equal: + return "=="; + + case ExpressionType.ExclusiveOr: + return "^"; + + case ExpressionType.GreaterThan: + return ">"; + + case ExpressionType.GreaterThanOrEqual: + return ">="; + + case ExpressionType.LeftShift: + return "<<"; + + case ExpressionType.LessThan: + return "<"; + + case ExpressionType.LessThanOrEqual: + return "<="; + + case ExpressionType.Modulo: + return "%"; + + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + return "*"; + + case ExpressionType.NotEqual: + return "!="; + + case ExpressionType.Or: + return "|"; + + case ExpressionType.OrElse: + return "||"; + + case ExpressionType.Power: + return "^"; + + case ExpressionType.RightShift: + return ">>"; + + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + return "-"; + + case ExpressionType.SubtractAssign: + return "-="; + } + return nodeType.ToString(); + } + + } + + private static StringBuilder AppendExpression(this StringBuilder builder, TypeBinaryExpression expression) + { + return builder.AppendExpression(expression.Expression); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, ConditionalExpression expression) + { + return builder.AppendExpression(expression.Test) + .Append(" ? ") + .AppendExpression(expression.IfTrue) + .Append(" : ") + .AppendExpression(expression.IfFalse); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, ParameterExpression expression) + { + return builder.Append(expression.Name ?? ""); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, MemberExpression expression) + { + if (expression.Expression != null) + { + builder.AppendExpression(expression.Expression); + } + else + { + builder.AppendNameOf(expression.Member.DeclaringType); + } + + return builder.Append('.') + .Append(expression.Member.Name); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, MethodCallExpression expression) + { + if (expression != null) + { + var paramFrom = 0; + var instance = expression.Object; + + if (expression.Method.IsExtensionMethod()) + { + paramFrom = 1; + instance = expression.Arguments[0]; + } + + if (instance != null) + { + builder.AppendExpression(instance); + } + else // Method is static + { + builder.AppendNameOf(expression.Method.DeclaringType); + } + + if (expression.Method.IsPropertyIndexerGetter()) + { + builder.AppendCommaSeparated("[", expression.Arguments.Skip(paramFrom), AppendExpression, "]"); + } + else if (expression.Method.IsPropertyIndexerSetter()) + { + builder.AppendCommaSeparated("[", expression.Arguments.Skip(paramFrom).Take(expression.Arguments.Count - paramFrom - 1), AppendExpression, "] = ") + .AppendExpression(expression.Arguments.Last()); + } + else if (expression.Method.IsPropertyGetter()) + { + builder.Append('.') + .Append(expression.Method.Name, 4); + if (expression.Arguments.Count > paramFrom) + { + builder.AppendCommaSeparated("[", expression.Arguments.Skip(paramFrom), AppendExpression, "]"); + } + } + else if (expression.Method.IsPropertySetter()) + { + builder.Append('.') + .Append(expression.Method.Name, 4) + .Append(" = ") + .AppendExpression(expression.Arguments.Last()); + } + else if (expression.Method.LooksLikeEventAttach()) + { + builder.Append('.') + .Append(expression.Method.Name, 4) + .Append(" += ") + .AppendCommaSeparated(expression.Arguments.Skip(paramFrom), AppendExpression); + } + else if (expression.Method.LooksLikeEventDetach()) + { + builder.Append('.') + .Append(expression.Method.Name, 7) + .Append(" -= ") + .AppendCommaSeparated(expression.Arguments.Skip(paramFrom), AppendExpression); + } + else + { + builder.Append('.') + .AppendNameOf(expression.Method, includeGenericArgumentList: true) + .AppendCommaSeparated("(", expression.Arguments.Skip(paramFrom), AppendExpression, ")"); + } + } + + return builder; + } + + private static StringBuilder AppendExpression(this StringBuilder builder, IndexExpression expression) + { + return builder.AppendExpression(expression.Object) + .AppendCommaSeparated("[", expression.Arguments, AppendExpression, "]"); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, LambdaExpression expression) + { + if (expression.Parameters.Count == 1) + { + builder.AppendExpression(expression.Parameters[0]); + } + else + { + builder.AppendCommaSeparated("(", expression.Parameters, AppendExpression, ")"); + } + return builder.Append(" => ") + .AppendExpression(expression.Body); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, NewExpression expression) + { + Type type = (expression.Constructor == null) ? expression.Type : expression.Constructor.DeclaringType; + return builder.Append("new ") + .AppendNameOf(type) + .AppendCommaSeparated("(", expression.Arguments, AppendExpression, ")"); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, NewArrayExpression expression) + { + switch (expression.NodeType) + { + case ExpressionType.NewArrayInit: + return builder.AppendCommaSeparated("new[] { ", expression.Expressions, AppendExpression, " }"); + + case ExpressionType.NewArrayBounds: + return builder.Append("new ") + .AppendNameOf(expression.Type.GetElementType()) + .AppendCommaSeparated("[", expression.Expressions, AppendExpression, "]"); + } + + return builder; // TODO: check whether this should be unreachable + } + + private static StringBuilder AppendExpression(this StringBuilder builder, InvocationExpression expression) + { + return builder.AppendExpression(expression.Expression) + .AppendCommaSeparated("(", expression.Arguments, AppendExpression, ")"); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, MemberInitExpression expression) + { + return builder.AppendExpression(expression.NewExpression) + .AppendCommaSeparated(" { ", expression.Bindings, AppendMemberBinding, " }"); + + StringBuilder AppendMemberBinding(StringBuilder b, MemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + var assignment = (MemberAssignment)binding; + return builder.Append(assignment.Member.Name) + .Append("= ") + .AppendExpression(assignment.Expression); + + case MemberBindingType.MemberBinding: + return b.AppendCommaSeparated(((MemberMemberBinding)binding).Bindings, AppendMemberBinding); + + case MemberBindingType.ListBinding: + var original = ((MemberListBinding)binding).Initializers; + for (int i = 0, n = original.Count; i < n; i++) + { + builder.AppendElementInit(original[i]); + } + return builder; + + default: + throw new Exception(string.Format(Resources.UnhandledBindingType, binding.BindingType)); + } + } + } + + private static StringBuilder AppendExpression(this StringBuilder builder, ListInitExpression expression) + { + return builder.AppendExpression(expression.NewExpression) + .AppendCommaSeparated(" { ", expression.Initializers, AppendElementInit, " }"); + } + + private static StringBuilder AppendExpression(this StringBuilder builder, MatchExpression expression) + { + return builder.AppendExpression(expression.Match.RenderExpression); + } + } +} diff --git a/src/Moq/StringBuilderExtensions.cs b/src/Moq/StringBuilderExtensions.cs index b236c35dd..c6655fc7a 100644 --- a/src/Moq/StringBuilderExtensions.cs +++ b/src/Moq/StringBuilderExtensions.cs @@ -12,8 +12,36 @@ namespace Moq { - internal static class StringBuilderExtensions + internal static partial class StringBuilderExtensions { + public static StringBuilder Append(this StringBuilder stringBuilder, string str, int startIndex) + { + return stringBuilder.Append(str, startIndex, str.Length - startIndex); + } + + public static StringBuilder AppendCommaSeparated(this StringBuilder stringBuilder, string prefix, IEnumerable source, Func append, string suffix) + { + return stringBuilder.Append(prefix) + .AppendCommaSeparated(source, append) + .Append(suffix); + } + + public static StringBuilder AppendCommaSeparated(this StringBuilder stringBuilder, IEnumerable source, Func append) + { + bool appendComma = false; + foreach (var item in source) + { + if (appendComma) + { + stringBuilder.Append(", "); + } + append(stringBuilder, item); + appendComma = true; + } + + return stringBuilder; + } + public static StringBuilder AppendIndented(this StringBuilder stringBuilder, string str, int count = 1, char indentChar = ' ') { var i = 0; @@ -41,32 +69,12 @@ public static StringBuilder AppendNameOf(this StringBuilder stringBuilder, Metho if (includeGenericArgumentList && method.IsGenericMethod) { - stringBuilder.Append('<'); - var genericArguments = method.GetGenericArguments(); - for (int i = 0, n = genericArguments.Length; i < n; ++i) - { - if (i > 0) - { - stringBuilder.Append(", "); - } - stringBuilder.AppendNameOf(genericArguments[i]); - } - stringBuilder.Append('>'); + stringBuilder.AppendCommaSeparated("<", method.GetGenericArguments(), AppendNameOf, ">"); } return stringBuilder; } - public static StringBuilder AppendNameOfAddEvent(this StringBuilder stringBuilder, MethodBase method, bool includeGenericArgumentList) - { - return stringBuilder.Append(method.Name.Substring("add_".Length)).Append(" += "); - } - - public static StringBuilder AppendNameOfRemoveEvent(this StringBuilder stringBuilder, MethodBase method, bool includeGenericArgumentList) - { - return stringBuilder.Append(method.Name.Substring("remove_".Length)).Append(" -= "); - } - public static StringBuilder AppendNameOf(this StringBuilder stringBuilder, Type type) { Debug.Assert(type != null); @@ -107,21 +115,6 @@ public static StringBuilder AppendParameterType(this StringBuilder stringBuilder return stringBuilder.AppendFormattedName(parameterType); } - public static StringBuilder AppendParameterTypeList(this StringBuilder stringBuilder, ParameterInfo[] parameters) - { - for (int i = 0; i < parameters.Length; ++i) - { - if (i > 0) - { - stringBuilder.Append(", "); - } - - stringBuilder.AppendParameterType(parameters[i]); - } - - return stringBuilder; - } - public static StringBuilder AppendValueOf(this StringBuilder stringBuilder, object obj) { if (obj == null) diff --git a/tests/Moq.Tests/ExpressionStringBuilderFixture.cs b/tests/Moq.Tests/ExpressionStringBuilderFixture.cs deleted file mode 100644 index 167a2984d..000000000 --- a/tests/Moq.Tests/ExpressionStringBuilderFixture.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD. -// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. - -using System; -using System.Linq.Expressions; - -using Xunit; - -namespace Moq.Tests -{ - public class ExpressionStringBuilderFixture - { - [Fact] - public void Formats_call_to_indexer_setter_method_using_indexer_syntax() - { - // foo => foo.set_Item("index", "value") - var foo = Expression.Parameter(typeof(IFoo), "foo"); - var expression = - Expression.Lambda>( - Expression.Call( - foo, - typeof(IFoo).GetProperty("Item").SetMethod, - Expression.Constant("index"), - Expression.Constant("value")), - foo); - - Assert.Equal(@"foo => foo[""index""] = ""value""", Format(expression)); - } - - [Fact] - public void Formats_ternary_conditional_expression_correctly() - { - // 1 == 2 ? 3 : 4 - var expression = Expression.Condition( - Expression.Equal(Expression.Constant(1), Expression.Constant(2)), - Expression.Constant(3), - Expression.Constant(4)); - - Assert.Equal(@"1 == 2 ? 3 : 4", Format(expression)); - } - - private static string Format(Expression expression) - { - return new ExpressionStringBuilder().Append(expression).ToString(); - } - - public interface IFoo - { - object this[object index] { get; set; } - } - } -} diff --git a/tests/Moq.Tests/ExtensionsFixture.cs b/tests/Moq.Tests/ExtensionsFixture.cs index df82077d0..a8b63c7e3 100644 --- a/tests/Moq.Tests/ExtensionsFixture.cs +++ b/tests/Moq.Tests/ExtensionsFixture.cs @@ -103,6 +103,37 @@ public void IsExtensionMethod_does_not_recognize_method_as_extension_method() Assert.False(thisMethod.IsExtensionMethod()); } + + [Theory] + [InlineData(nameof(IMethods.Empty), "")] + [InlineData(nameof(IMethods.Int), "int")] + [InlineData(nameof(IMethods.IntAndString), "int, string")] + [InlineData(nameof(IMethods.InInt), "in int")] + [InlineData(nameof(IMethods.RefInt), "ref int")] + [InlineData(nameof(IMethods.OutInt), "out int")] + [InlineData(nameof(IMethods.BoolAndParamsString), "bool, params string[]")] + public void GetParameterTypeList_formats_parameter_lists_correctly(string methodName, string expected) + { + var actual = GetParameterTypeList(methodName); + Assert.Equal(expected, actual); + } + + private string GetParameterTypeList(string methodName) + { + var method = typeof(IMethods).GetMethod(methodName); + return method.GetParameterTypeList(); + } + + public interface IMethods + { + void Empty(); + void Int(int arg1); + void IntAndString(int arg1, string arg2); + void InInt(in int arg1); + void RefInt(ref int arg1); + void OutInt(out int arg1); + void BoolAndParamsString(bool arg1, params string[] arg2); + } } public interface IFooReset diff --git a/tests/Moq.Tests/StringBuilderExtensionsFixture.cs b/tests/Moq.Tests/StringBuilderExtensionsFixture.cs index cf18c7303..ed7fe5ce7 100644 --- a/tests/Moq.Tests/StringBuilderExtensionsFixture.cs +++ b/tests/Moq.Tests/StringBuilderExtensionsFixture.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD. // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. +using System; +using System.Linq.Expressions; using System.Text; using Xunit; @@ -9,37 +11,43 @@ namespace Moq.Tests { public class StringBuilderExtensionsFixture { - [Theory] - [InlineData(nameof(IMethods.Empty), "")] - [InlineData(nameof(IMethods.Int), "int")] - [InlineData(nameof(IMethods.IntAndString), "int, string")] - [InlineData(nameof(IMethods.InInt), "in int")] - [InlineData(nameof(IMethods.RefInt), "ref int")] - [InlineData(nameof(IMethods.OutInt), "out int")] - [InlineData(nameof(IMethods.BoolAndParamsString), "bool, params string[]")] - public void AppendParameterList_formats_parameter_lists_correctly(string methodName, string expected) + [Fact] + public void AppendExpression_formats_call_to_indexer_setter_method_using_indexer_syntax() { - var actual = GetFormattedParameterListOf(methodName); - Assert.Equal(expected, actual); + // foo => foo.set_Item("index", "value") + var foo = Expression.Parameter(typeof(IFoo), "foo"); + var expression = + Expression.Lambda>( + Expression.Call( + foo, + typeof(IFoo).GetProperty("Item").SetMethod, + Expression.Constant("index"), + Expression.Constant("value")), + foo); + + Assert.Equal(@"foo => foo[""index""] = ""value""", GetAppendExpressionResult(expression)); + } + + [Fact] + public void AppendExpression_formats_ternary_conditional_expression_correctly() + { + // 1 == 2 ? 3 : 4 + var expression = Expression.Condition( + Expression.Equal(Expression.Constant(1), Expression.Constant(2)), + Expression.Constant(3), + Expression.Constant(4)); + + Assert.Equal(@"1 == 2 ? 3 : 4", GetAppendExpressionResult(expression)); } - private string GetFormattedParameterListOf(string methodName) + private string GetAppendExpressionResult(Expression expression) { - var stringBuilder = new StringBuilder(); - var method = typeof(IMethods).GetMethod(methodName); - stringBuilder.AppendParameterTypeList(method.GetParameters()); - return stringBuilder.ToString(); + return new StringBuilder().AppendExpression(expression).ToString(); } - public interface IMethods + public interface IFoo { - void Empty(); - void Int(int arg1); - void IntAndString(int arg1, string arg2); - void InInt(in int arg1); - void RefInt(ref int arg1); - void OutInt(out int arg1); - void BoolAndParamsString(bool arg1, params string[] arg2); + object this[object index] { get; set; } } } }