From bdc2ea81c0da8ca6e0c7633c4b37769de216e995 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 31 Dec 2018 12:54:54 -0800 Subject: [PATCH] Unify TypeNameHelpers (dotnet/extensions#876) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/2f53c3619543bb2b7a166c1926ea462b8521d2fc --- src/Shared/TypeNameHelper/TypeNameHelper.cs | 59 ++++++++++++------- .../test/Shared.Tests/TypeNameHelperTest.cs | 41 +++++++++++++ 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/Shared/TypeNameHelper/TypeNameHelper.cs b/src/Shared/TypeNameHelper/TypeNameHelper.cs index 1cc7468646..3994a074b6 100644 --- a/src/Shared/TypeNameHelper/TypeNameHelper.cs +++ b/src/Shared/TypeNameHelper/TypeNameHelper.cs @@ -9,6 +9,8 @@ namespace Microsoft.Extensions.Internal { internal class TypeNameHelper { + private const char DefaultNestedTypeDelimiter = '+'; + private static readonly Dictionary _builtInTypeNames = new Dictionary { { typeof(void), "void" }, @@ -40,15 +42,17 @@ namespace Microsoft.Extensions.Internal /// The . /// true to print a fully qualified name. /// true to include generic parameter names. + /// true to include generic parameters. + /// Character to use as a delimiter in nested type names /// The pretty printed type name. - public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) + public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter) { var builder = new StringBuilder(); - ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); + ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter)); return builder.ToString(); } - private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) + private static void ProcessType(StringBuilder builder, Type type, in DisplayNameOptions options) { if (type.IsGenericType) { @@ -72,11 +76,17 @@ namespace Microsoft.Extensions.Internal } else { - builder.Append(options.FullName ? type.FullName : type.Name); + var name = options.FullName ? type.FullName : type.Name; + builder.Append(name); + + if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) + { + builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length); + } } } - private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) + private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options) { var innerType = type; while (innerType.IsArray) @@ -95,7 +105,7 @@ namespace Microsoft.Extensions.Internal } } - private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options) { var offset = 0; if (type.IsNested) @@ -108,7 +118,7 @@ namespace Microsoft.Extensions.Internal if (type.IsNested) { ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); - builder.Append('+'); + builder.Append(options.NestedTypeDelimiter); } else if (!string.IsNullOrEmpty(type.Namespace)) { @@ -126,35 +136,44 @@ namespace Microsoft.Extensions.Internal builder.Append(type.Name, 0, genericPartIndex); - builder.Append('<'); - for (var i = offset; i < length; i++) + if (options.IncludeGenericParameters) { - ProcessType(builder, genericArguments[i], options); - if (i + 1 == length) + builder.Append('<'); + for (var i = offset; i < length; i++) { - continue; - } + ProcessType(builder, genericArguments[i], options); + if (i + 1 == length) + { + continue; + } - builder.Append(','); - if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) - { - builder.Append(' '); + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + { + builder.Append(' '); + } } + builder.Append('>'); } - builder.Append('>'); } - private struct DisplayNameOptions + private readonly struct DisplayNameOptions { - public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter) { FullName = fullName; + IncludeGenericParameters = includeGenericParameters; IncludeGenericParameterNames = includeGenericParameterNames; + NestedTypeDelimiter = nestedTypeDelimiter; } public bool FullName { get; } + public bool IncludeGenericParameters { get; } + public bool IncludeGenericParameterNames { get; } + + public char NestedTypeDelimiter { get; } } } } diff --git a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs index b7f4285bdc..bd29f647d1 100644 --- a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs +++ b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs @@ -216,6 +216,47 @@ namespace Microsoft.Extensions.Internal Assert.Equal(expected, actual); } + public static TheoryData FullTypeNameData + { + get + { + return new TheoryData + { + // Predefined Types + { typeof(int), "int" }, + { typeof(List), "System.Collections.Generic.List" }, + { typeof(Dictionary), "System.Collections.Generic.Dictionary" }, + { typeof(Dictionary>), "System.Collections.Generic.Dictionary" }, + { typeof(List>), "System.Collections.Generic.List" }, + + // Classes inside NonGeneric class + { typeof(A), "Microsoft.Extensions.Internal.TypeNameHelperTest.A" }, + { typeof(B), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + { typeof(C), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(C>), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(B>), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + + // Classes inside Generic class + { typeof(Outer.D), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.D" }, + { typeof(Outer.E), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" }, + { typeof(Outer.F), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.F.E>),"Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.E.E>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" } + }; + } + } + + [Theory] + [MemberData(nameof(FullTypeNameData))] + public void Can_PrettyPrint_FullTypeName_WithoutGenericParametersAndNestedTypeDelimiter(Type type, string expectedTypeName) + { + // Arrange & Act + var displayName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameters: false, nestedTypeDelimiter: '.'); + + // Assert + Assert.Equal(expectedTypeName, displayName); + } + private class A { } private class B { }