Skip to content
Permalink
Browse files

Change the ObjectToTypeName value converter to give prettier C# names. (

  • Loading branch information
dfkeenan authored and xen2 committed Mar 26, 2019
1 parent 7b86347 commit 89e92e45368301f11cd884cc3d365ed0caebf014
@@ -0,0 +1,47 @@
// Copyright (c) Xenko contributors (https://xenko.com)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xenko.Core.Presentation.Extensions;
using Xunit;

namespace Xenko.Core.Presentation.Tests
{
public class TestTypeExtensions
{
[Fact]
public void TestTypeExtensionsToSimpleCSharpNameReturnsCorrectlyFormattedName()
{
var tests = new List<(Type Type, string Name)>()
{
(typeof(int), "int"), //Simple type
(typeof(int?), "int?"), //Nullable simple type
(typeof(TimeSpan), "TimeSpan"), //Type
(typeof(TimeSpan?), "TimeSpan?"), //Nullable type
(typeof(int[]), "int[]"), //Array of simple type
(typeof(int?[]), "int?[]"), //Array of nullable simple type
(typeof(TimeSpan[]), "TimeSpan[]"), //Array of type
(typeof(TimeSpan?[]), "TimeSpan?[]"), //Array of nullable type
(typeof(TimeSpan[,,]), "TimeSpan[,,]"), //Multi-dimesional array of type
(typeof(Dictionary<string, FactAttribute>), "Dictionary<string, FactAttribute>"), //Generic type
(typeof((string, FactAttribute)), "(string, FactAttribute)"), //Tuple types
(typeof((string, FactAttribute)?), "(string, FactAttribute)?"), //Nullable tuple types
(typeof(Dictionary<string, GenericStruct<int?>>), "Dictionary<string, GenericStruct<int?>>"), //Nested generic type
(typeof((GenericStruct<int?>?, double)), "(GenericStruct<int?>?, double)"), //Crazy type
};

foreach (var item in tests)
{
Assert.Equal(item.Type.ToSimpleCSharpName(), item.Name);
}
}

private struct GenericStruct<T>
{
public T Field;
}
}
}
@@ -0,0 +1,32 @@
// Copyright (c) Xenko contributors (https://xenko.com)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xenko.Core.Presentation.ValueConverters;
using Xunit;

namespace Xenko.Core.Presentation.Tests
{
public class TestValueConverters
{
[Fact]
public void TestObjectToTypeNameConvertsNullToNone()
{
var converter = new ObjectToTypeName();

Assert.Equal(converter.Convert(null, typeof(string), null, CultureInfo.CurrentCulture), ObjectToTypeName.NullObjectType);
}

[Fact]
public void TestObjectToTypeNameConverterValueToType()
{
var converter = new ObjectToTypeName();

Assert.NotEqual(converter.Convert("hello", typeof(string), null, CultureInfo.CurrentCulture), ObjectToTypeName.NullObjectType);
}
}
}
@@ -12,6 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1"/>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
@@ -0,0 +1,79 @@
// Copyright (c) Xenko contributors (https://xenko.com)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.CodeDom;
using Microsoft.CSharp;
using System.Linq;
using System.Collections.Generic;

namespace Xenko.Core.Presentation.Extensions
{
/// <summary>
/// Helper class to get formatted names from <see cref="Type"/>.
/// </summary>
internal static class TypeExtensions
{
private static readonly CSharpCodeProvider codeProvider = new CSharpCodeProvider();
private static readonly HashSet<Type> valueTupleTypes = new HashSet<Type>
{
typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>),
};

private static bool IsValueTuple(Type type) =>
type.IsGenericType &&
valueTupleTypes.Contains(type.GetGenericTypeDefinition());

/// <summary>
/// Gets C# syntax like type declaration from <see cref="Type"/>
/// </summary>
/// <param name="type">The type to get the name from.</param>
/// <returns>C# syntax like type declaration from the type provided</returns>
/// <exception cref="ArgumentNullException">If type parameter is null.</exception>
public static string ToSimpleCSharpName(this Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}

if (type.IsArray)
{
return ToSimpleCSharpName(type.GetElementType()) + "[" + new string(',', type.GetArrayRank() - 1) + "]";
}

if (!type.IsGenericType)
{
//Use a CSharpCodeProvider to handle conversions like 'Int32' to 'int'
var fullTypeName = codeProvider.GetTypeOutput(new CodeTypeReference(type));

var simpleNameStart = fullTypeName.LastIndexOf('.') + 1;

if (simpleNameStart >= fullTypeName.Length)
return fullTypeName;

return fullTypeName.Substring(simpleNameStart);
}

if (Nullable.GetUnderlyingType(type) is Type nullableType)
{
return ToSimpleCSharpName(nullableType) + "?";
}

var genericParameters = string.Join(", ", type.GetGenericArguments().Select(ToSimpleCSharpName));

if (IsValueTuple(type))
{
return "(" + genericParameters + ")";
}

return type.Name.Substring(0, type.Name.LastIndexOf('`')) + "<" + genericParameters + ">";
}
}
}
@@ -2,7 +2,9 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Xenko.Core.Annotations;
using Xenko.Core.Presentation.Extensions;

namespace Xenko.Core.Presentation.ValueConverters
{
@@ -23,7 +25,7 @@ public class ObjectToTypeName : OneWayValueConverter<ObjectToTypeName>
[NotNull]
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.GetType().Name ?? NullObjectType;
return value?.GetType().ToSimpleCSharpName() ?? NullObjectType;
}
}
}

0 comments on commit 89e92e4

Please sign in to comment.
You can’t perform that action at this time.