Skip to content

Commit 41ccb01

Browse files
authored
Merge pull request #25 from StanleyGoldman/customize-anonymous-object
Customize output of anonymous objects
2 parents 80bb593 + f5f5c3e commit 41ccb01

17 files changed

+119
-65
lines changed

ReadableExpressions.UnitTests/Extensions/WhenGettingFriendlyNames.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ public void ShouldUseFriendlyNamesForAnonymousTypes()
4646
friendlyName.ShouldBe("AnonymousType<int, string>");
4747
}
4848

49+
// See https://github.com/agileobjects/ReadableExpressions/pull/25
50+
[Fact]
51+
public void ShouldUseAnonymousTypeNameFactoryIfConfigured()
52+
{
53+
var anon = new { One = 1, Two = "two" };
54+
55+
var friendlyName = anon.GetType().GetFriendlyName(c => c.NameAnonymousTypesUsing(t => "object"));
56+
57+
friendlyName.ShouldBe("object");
58+
}
59+
4960
// See https://github.com/agileobjects/ReadableExpressions/issues/6
5061
[Fact]
5162
public void ShouldUseFriendlyNamesForMultiplyNestedTypes()

ReadableExpressions/Extensions/PublicTypeExtensions.cs

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using NetStandardPolyfills;
8+
using static TranslationContext;
89

910
/// <summary>
1011
/// Provides a set of static extension methods for type information.
@@ -15,8 +16,12 @@ public static class PublicTypeExtensions
1516
/// Returns a friendly, readable version of the name of the given <paramref name="type"/>.
1617
/// </summary>
1718
/// <param name="type">The type for which to retrieve a friendly, readable name.</param>
19+
/// <param name="configuration">The configuration to use for the variable naming, if required.</param>
1820
/// <returns>A friendly, readable version of the name of the given <paramref name="type"/>.</returns>
19-
public static string GetFriendlyName(this Type type)
21+
public static string GetFriendlyName(this Type type, Func<TranslationSettings, TranslationSettings> configuration = null)
22+
=> GetFriendlyName(type, GetTranslationSettings(configuration));
23+
24+
internal static string GetFriendlyName(this Type type, TranslationSettings translationSettings)
2025
{
2126
if (type.FullName == null)
2227
{
@@ -26,7 +31,7 @@ public static string GetFriendlyName(this Type type)
2631

2732
if (type.IsArray)
2833
{
29-
return type.GetElementType().GetFriendlyName() + "[]";
34+
return type.GetElementType().GetFriendlyName(translationSettings) + "[]";
3035
}
3136

3237
if (!type.IsGenericType())
@@ -35,7 +40,7 @@ public static string GetFriendlyName(this Type type)
3540

3641
if (type.IsNested)
3742
{
38-
return type.DeclaringType.GetFriendlyName() + "." + qualifiedTypeName;
43+
return type.DeclaringType.GetFriendlyName(translationSettings) + "." + qualifiedTypeName;
3944
}
4045

4146
return qualifiedTypeName;
@@ -45,16 +50,16 @@ public static string GetFriendlyName(this Type type)
4550

4651
if ((underlyingNullableType = Nullable.GetUnderlyingType(type)) != null)
4752
{
48-
return underlyingNullableType.GetFriendlyName() + "?";
53+
return underlyingNullableType.GetFriendlyName(translationSettings) + "?";
4954
}
5055

51-
return GetGenericTypeName(type);
56+
return GetGenericTypeName(type, translationSettings);
5257
}
5358

54-
private static string GetGenericTypeName(Type genericType)
59+
private static string GetGenericTypeName(Type genericType, TranslationSettings settings)
5560
{
5661
var typeGenericTypeArguments = genericType.GetGenericTypeArguments();
57-
var genericTypeName = GetGenericTypeName(genericType.Name, typeGenericTypeArguments.Length, typeGenericTypeArguments);
62+
var genericTypeName = GetGenericTypeName(genericType, typeGenericTypeArguments.Length, typeGenericTypeArguments, settings);
5863

5964
if (!genericType.IsNested)
6065
{
@@ -102,7 +107,7 @@ private static string GetGenericTypeName(Type genericType)
102107
typeGenericTypeArguments = typeGenericTypeArgumentsSubset;
103108
}
104109

105-
parentTypeName = GetGenericTypeName(parentTypeName, numberOfParameters, typeArguments);
110+
parentTypeName = GetGenericTypeName(genericType, numberOfParameters, typeArguments, settings);
106111
}
107112

108113
genericTypeName = parentTypeName + "." + genericTypeName;
@@ -112,29 +117,36 @@ private static string GetGenericTypeName(Type genericType)
112117
}
113118

114119
private static string GetGenericTypeName(
115-
string typeName,
120+
Type type,
116121
int numberOfParameters,
117-
IEnumerable<Type> typeArguments)
122+
IEnumerable<Type> typeArguments,
123+
TranslationSettings settings)
118124
{
125+
var anonTypeIndex = 0;
126+
127+
var isAnonType =
128+
type.Name.StartsWith('<') &&
129+
((anonTypeIndex = type.Name.IndexOf("AnonymousType", StringComparison.Ordinal)) != -1);
130+
131+
if (isAnonType && (settings.AnonymousTypeNameFactory != null))
132+
{
133+
return settings.AnonymousTypeNameFactory.Invoke(type);
134+
}
135+
136+
var typeName = type.Name;
137+
119138
var typeGenericTypeArgumentFriendlyNames =
120-
typeArguments.Project(GetFriendlyName).Join(", ");
139+
typeArguments.Project(t => GetFriendlyName(t, settings)).Join(", ");
121140

122141
typeName = typeName.Replace(
123142
"`" + numberOfParameters,
124143
"<" + typeGenericTypeArgumentFriendlyNames + ">");
125144

126-
return typeName.StartsWith('<') ? GetAnonymousTypeName(typeName) : typeName;
145+
return isAnonType ? GetAnonymousTypeName(typeName, anonTypeIndex) : typeName;
127146
}
128147

129-
private static string GetAnonymousTypeName(string typeName)
148+
private static string GetAnonymousTypeName(string typeName, int anonTypeIndex)
130149
{
131-
var anonTypeIndex = typeName.IndexOf("AnonymousType", StringComparison.Ordinal);
132-
133-
if (anonTypeIndex == -1)
134-
{
135-
return typeName;
136-
}
137-
138150
typeName = typeName.Substring(anonTypeIndex);
139151

140152
var trimStartIndex = "AnonymousType".Length;
@@ -149,27 +161,37 @@ private static string GetAnonymousTypeName(string typeName)
149161
/// Retrieves a camel-case variable name for a variable of this <paramref name="type"/>.
150162
/// </summary>
151163
/// <param name="type">The Type for which to retrieve the variable name.</param>
164+
/// <param name="configuration">The configuration to use for the variable naming, if required.</param>
152165
/// <returns>A camel-case variable name for a variable of this <paramref name="type"/>.</returns>
153-
public static string GetVariableNameInCamelCase(this Type type) => GetVariableName(type).ToCamelCase();
166+
public static string GetVariableNameInCamelCase(this Type type, Func<TranslationSettings, TranslationSettings> configuration = null)
167+
=> GetVariableNameInCamelCase(type, GetTranslationSettings(configuration));
168+
169+
internal static string GetVariableNameInCamelCase(this Type type, TranslationSettings settings)
170+
=> GetVariableName(type, settings).ToCamelCase();
154171

155172
/// <summary>
156173
/// Retrieves a pascal-case variable name for a variable of this <paramref name="type"/>.
157174
/// </summary>
158175
/// <param name="type">The Type for which to retrieve the variable name.</param>
176+
/// <param name="configuration">The configuration to use for the variable naming, if required.</param>
159177
/// <returns>A pascal-case variable name for a variable of this <paramref name="type"/>.</returns>
160-
public static string GetVariableNameInPascalCase(this Type type) => GetVariableName(type).ToPascalCase();
178+
public static string GetVariableNameInPascalCase(this Type type, Func<TranslationSettings, TranslationSettings> configuration = null)
179+
=> GetVariableNameInPascalCase(type, GetTranslationSettings(configuration));
180+
181+
internal static string GetVariableNameInPascalCase(this Type type, TranslationSettings settings)
182+
=> GetVariableName(type, settings).ToPascalCase();
161183

162-
private static string GetVariableName(Type type)
184+
private static string GetVariableName(Type type, TranslationSettings settings)
163185
{
164186
if (type.IsArray)
165187
{
166-
return GetVariableName(type.GetElementType()) + "Array";
188+
return GetVariableName(type.GetElementType(), settings) + "Array";
167189
}
168190

169191
var typeIsEnumerable = type.IsEnumerable();
170192
var typeIsDictionary = typeIsEnumerable && type.IsDictionary();
171193
var namingType = (typeIsEnumerable && !typeIsDictionary) ? type.GetEnumerableElementType() : type;
172-
var variableName = GetBaseVariableName(namingType);
194+
var variableName = GetBaseVariableName(namingType, settings);
173195

174196
if (namingType.IsInterface())
175197
{
@@ -178,31 +200,34 @@ private static string GetVariableName(Type type)
178200

179201
if (namingType.IsGenericType())
180202
{
181-
variableName = GetGenericTypeVariableName(variableName, namingType);
203+
variableName = GetGenericTypeVariableName(variableName, namingType, settings);
182204
}
183205

184206
variableName = RemoveLeadingNonAlphaNumerics(variableName);
185207

186208
return (typeIsDictionary || !typeIsEnumerable) ? variableName : variableName.Pluralise();
187209
}
188210

189-
private static string GetBaseVariableName(Type namingType)
190-
=> namingType.IsPrimitive() ? namingType.GetFriendlyName() : namingType.Name;
211+
private static string GetBaseVariableName(Type namingType, TranslationSettings translationSettings)
212+
=> namingType.IsPrimitive() ? namingType.GetFriendlyName(translationSettings) : namingType.Name;
191213

192-
private static string GetGenericTypeVariableName(string variableName, Type namingType)
214+
private static string GetGenericTypeVariableName(
215+
string variableName,
216+
Type namingType,
217+
TranslationSettings settings)
193218
{
194219
var nonNullableType = namingType.GetNonNullableType();
195220
var genericTypeArguments = namingType.GetGenericTypeArguments();
196221

197222
if (nonNullableType != namingType)
198223
{
199-
return "nullable" + genericTypeArguments[0].GetVariableNameInPascalCase();
224+
return "nullable" + genericTypeArguments[0].GetVariableNameInPascalCase(settings);
200225
}
201226

202227
variableName = variableName.Substring(0, variableName.IndexOf('`'));
203228

204229
variableName += genericTypeArguments
205-
.Project(arg => "_" + arg.GetVariableNameInPascalCase())
230+
.Project(arg => "_" + arg.GetVariableNameInPascalCase(settings))
206231
.Join(string.Empty);
207232

208233
return variableName;

ReadableExpressions/TranslationContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static TranslationContext For(
7070
return new TranslationContext(analyzer, globalTranslator, settings);
7171
}
7272

73-
private static TranslationSettings GetTranslationSettings(
73+
internal static TranslationSettings GetTranslationSettings(
7474
Func<TranslationSettings, TranslationSettings> configuration)
7575
{
7676
return configuration?.Invoke(new TranslationSettings()) ?? TranslationSettings.Default;

ReadableExpressions/TranslationSettings.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace AgileObjects.ReadableExpressions
22
{
3+
using System;
4+
35
/// <summary>
46
/// Provides configuration options to control aspects of source-code string generation.
57
/// </summary>
@@ -42,5 +44,18 @@ public TranslationSettings ShowQuotedLambdaComments
4244
internal bool DoNotCommentQuotedLambdas => !CommentQuotedLambdas;
4345

4446
internal bool CommentQuotedLambdas { get; set; }
47+
48+
/// <summary>
49+
/// Name anonymous types using the given <paramref name="nameFactory"/> instead of the
50+
/// default method.
51+
/// </summary>
52+
/// <param name="nameFactory">The factory method to execute to retrieve the name for an anonymous type.</param>
53+
public TranslationSettings NameAnonymousTypesUsing(Func<Type, string> nameFactory)
54+
{
55+
AnonymousTypeNameFactory = nameFactory;
56+
return this;
57+
}
58+
59+
internal Func<Type, string> AnonymousTypeNameFactory { get; private set; }
4560
}
4661
}

ReadableExpressions/Translators/AssignmentExpressionTranslator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ internal static string GetAssignment(
5656
var symbol = _symbolsByNodeType[assignmentType];
5757

5858
var valueString = (value.NodeType == ExpressionType.Default)
59-
? DefaultExpressionTranslator.Translate((DefaultExpression)value)
59+
? DefaultExpressionTranslator.Translate((DefaultExpression)value, context.Settings)
6060
: GetValueTranslation(value, context);
6161

6262
var assignment = target + " " + symbol;

ReadableExpressions/Translators/BlockExpressionTranslator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private static IList<string> GetVariableDeclarations(
4545
.GroupBy(v => v.Type)
4646
.Project(vGrp => new
4747
{
48-
TypeName = vGrp.Key.GetFriendlyName(),
48+
TypeName = vGrp.Key.GetFriendlyName(context.Settings),
4949
VariableNames = vGrp.Project(variable => ParameterExpressionTranslator.Translate(variable, context))
5050
})
5151
.Project(varData => $"{varData.TypeName} {varData.VariableNames.Join(", ")};")
@@ -160,7 +160,7 @@ private static string GetTerminatedStatementOrNull(Expression expression, Transl
160160
return translation;
161161
}
162162

163-
var typeName = GetVariableTypeName((BinaryExpression)expression);
163+
var typeName = GetVariableTypeName((BinaryExpression)expression, context.Settings);
164164

165165
return typeName + " " + translation;
166166
}
@@ -181,9 +181,9 @@ private static bool StatementIsTerminated(string translation, Expression express
181181
return translation.IsTerminated() || expression.IsComment();
182182
}
183183

184-
private static string GetVariableTypeName(BinaryExpression assignment)
184+
private static string GetVariableTypeName(BinaryExpression assignment, TranslationSettings translationSettings)
185185
{
186-
return UseFullTypeName(assignment) ? assignment.Left.Type.GetFriendlyName() : "var";
186+
return UseFullTypeName(assignment) ? assignment.Left.Type.GetFriendlyName(translationSettings) : "var";
187187
}
188188

189189
private static bool UseFullTypeName(BinaryExpression assignment)

ReadableExpressions/Translators/CastExpressionTranslator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private static string TranslateCast(Expression expression, TranslationContext co
6464
#endif
6565

6666
var methodSubject = subjectMethod.IsStatic
67-
? subjectMethod.DeclaringType.GetFriendlyName()
67+
? subjectMethod.DeclaringType.GetFriendlyName(context.Settings)
6868
: context.Translate(methodCall.Arguments.ElementAtOrDefault(1));
6969

7070
return methodSubject + "." + subjectMethod.Name;
@@ -76,7 +76,7 @@ private static string TranslateCast(Expression expression, TranslationContext co
7676
private static string TranslateMethodConversion(UnaryExpression cast, TranslationContext context)
7777
{
7878
return MethodCallExpressionTranslator.GetMethodCall(
79-
cast.Method.DeclaringType.GetFriendlyName(),
79+
cast.Method.DeclaringType.GetFriendlyName(context.Settings),
8080
new BclMethodInfoWrapper(cast.Method),
8181
new[] { cast.Operand },
8282
cast,
@@ -96,7 +96,7 @@ public static string Translate(
9696
Type resultType,
9797
TranslationContext context)
9898
{
99-
var typeName = resultType.GetFriendlyName();
99+
var typeName = resultType.GetFriendlyName(context.Settings);
100100
var subject = context.Translate(castValue);
101101

102102
if (castValue.NodeType == ExpressionType.Assign)
@@ -115,7 +115,7 @@ public static string Translate(
115115
private static string TranslateTypeAs(Expression expression, TranslationContext context)
116116
{
117117
var typeAs = (UnaryExpression)expression;
118-
var typeName = typeAs.Type.GetFriendlyName();
118+
var typeName = typeAs.Type.GetFriendlyName(context.Settings);
119119
var subject = context.Translate(typeAs.Operand);
120120

121121
return $"({subject} as {typeName})";
@@ -124,7 +124,7 @@ private static string TranslateTypeAs(Expression expression, TranslationContext
124124
private static string TranslateTypeIs(Expression expression, TranslationContext context)
125125
{
126126
var typeIs = (TypeBinaryExpression)expression;
127-
var typeName = typeIs.TypeOperand.GetFriendlyName();
127+
var typeName = typeIs.TypeOperand.GetFriendlyName(context.Settings);
128128
var subject = context.Translate(typeIs.Expression);
129129

130130
return $"({subject} is {typeName})";

ReadableExpressions/Translators/ConstantExpressionTranslator.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public string Translate(Expression expression, TranslationContext context)
3333

3434
if (constant.Type.IsEnum())
3535
{
36-
return constant.Type.GetFriendlyName() + "." + constant.Value;
36+
return constant.Type.GetFriendlyName(context.Settings) + "." + constant.Value;
3737
}
3838

3939
if (TryTranslateByTypeCode(constant, context, out var translation))
@@ -48,7 +48,7 @@ public string Translate(Expression expression, TranslationContext context)
4848
return constant.Value.ToString();
4949
}
5050

51-
return valueType.GetFriendlyName();
51+
return valueType.GetFriendlyName(context.Settings);
5252
}
5353

5454
private static bool TryTranslateByTypeCode(
@@ -92,7 +92,7 @@ private static bool TryTranslateByTypeCode(
9292
return true;
9393

9494
case NetStandardTypeCode.Object:
95-
if (IsType(constant, out translation) ||
95+
if (IsType(constant, context.Settings, out translation) ||
9696
IsLambda(constant, context, out translation) ||
9797
IsFunc(constant, out translation) ||
9898
IsRegex(constant, out translation) ||
@@ -243,11 +243,12 @@ private static string FormatNumeric(float value)
243243
return (value % 1).Equals(0) ? value.ToString("0") : value.ToString(CultureInfo.CurrentCulture);
244244
}
245245

246-
private static bool IsType(ConstantExpression constant, out string translation)
246+
private static bool IsType(ConstantExpression constant,
247+
TranslationSettings translationSettings, out string translation)
247248
{
248249
if (constant.Type.IsAssignableTo(typeof(Type)))
249250
{
250-
translation = $"typeof({((Type)constant.Value).GetFriendlyName()})";
251+
translation = $"typeof({((Type)constant.Value).GetFriendlyName(translationSettings)})";
251252
return true;
252253
}
253254

0 commit comments

Comments
 (0)