mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 21:38:16 +08:00
Adding better type names for return types and parameters
Uses the typenamehelper from Ben.Demystifer to help break down things like generic lists into their actual type display name.
This commit is contained in:

committed by
Patrik Svensson

parent
a0e20f299c
commit
78958aae27
@ -19,11 +19,7 @@ internal static class ExceptionFormatter
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
return new Rows(new IRenderable[]
|
||||
{
|
||||
GetMessage(exception, settings),
|
||||
GetStackFrames(exception, settings),
|
||||
}).Expand();
|
||||
return new Rows(GetMessage(exception, settings), GetStackFrames(exception, settings)).Expand();
|
||||
}
|
||||
|
||||
private static Markup GetMessage(Exception ex, ExceptionSettings settings)
|
||||
@ -78,6 +74,13 @@ internal static class ExceptionFormatter
|
||||
builder.Append("async ");
|
||||
}
|
||||
|
||||
if (method is MethodInfo mi)
|
||||
{
|
||||
var returnParameter = mi.ReturnParameter;
|
||||
builder.AppendWithStyle(styles.ParameterType, GetParameterName(returnParameter).EscapeMarkup());
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
|
||||
builder.AppendWithStyle(styles.Parenthesis, "(");
|
||||
AppendParameters(builder, method, settings);
|
||||
@ -114,7 +117,9 @@ internal static class ExceptionFormatter
|
||||
{
|
||||
var typeColor = settings.Style.ParameterType.ToMarkup();
|
||||
var nameColor = settings.Style.ParameterName.ToMarkup();
|
||||
var parameters = method?.GetParameters().Select(x => $"[{typeColor}]{x.ParameterType.Name.EscapeMarkup()}[/] [{nameColor}]{x.Name?.EscapeMarkup()}[/]");
|
||||
var parameters = method?.GetParameters()
|
||||
.Select(x => $"[{typeColor}]{GetParameterName(x).EscapeMarkup()}[/] [{nameColor}]{x.Name?.EscapeMarkup()}[/]");
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
builder.Append(string.Join(", ", parameters));
|
||||
@ -150,7 +155,8 @@ internal static class ExceptionFormatter
|
||||
}
|
||||
}
|
||||
|
||||
private static string Emphasize(string input, char[] separators, Style color, bool compact, ExceptionSettings settings)
|
||||
private static string Emphasize(string input, char[] separators, Style color, bool compact,
|
||||
ExceptionSettings settings)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
@ -240,6 +246,46 @@ internal static class ExceptionFormatter
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPrefix(ParameterInfo parameter)
|
||||
{
|
||||
if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false))
|
||||
{
|
||||
return "params";
|
||||
}
|
||||
|
||||
if (parameter.IsOut)
|
||||
{
|
||||
return "out";
|
||||
}
|
||||
|
||||
if (parameter.IsIn)
|
||||
{
|
||||
return "in";
|
||||
}
|
||||
|
||||
if (parameter.ParameterType.IsByRef)
|
||||
{
|
||||
return "ref";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string GetParameterName(ParameterInfo parameter)
|
||||
{
|
||||
var prefix = GetPrefix(parameter);
|
||||
var parameterType = parameter.ParameterType;
|
||||
|
||||
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType)
|
||||
{
|
||||
parameterType = elementType;
|
||||
}
|
||||
|
||||
var typeName = TypeNameHelper.GetTypeDisplayName(parameterType, false, true);
|
||||
|
||||
return string.IsNullOrWhiteSpace(prefix) ? typeName : $"{prefix} {typeName}";
|
||||
}
|
||||
|
||||
private static string GetMethodName(ref MethodBase method, out bool isAsync)
|
||||
{
|
||||
var declaringType = method.DeclaringType;
|
||||
@ -270,9 +316,9 @@ internal static class ExceptionFormatter
|
||||
builder.Append(method.Name);
|
||||
if (method.IsGenericMethod)
|
||||
{
|
||||
builder.Append('[');
|
||||
builder.Append('<');
|
||||
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
|
||||
builder.Append(']');
|
||||
builder.Append('>');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
@ -281,7 +327,8 @@ internal static class ExceptionFormatter
|
||||
private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
|
||||
{
|
||||
// https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L400-L455
|
||||
declaringType = method.DeclaringType ?? throw new ArgumentException("Method must have a declaring type.", nameof(method));
|
||||
declaringType = method.DeclaringType ??
|
||||
throw new ArgumentException("Method must have a declaring type.", nameof(method));
|
||||
|
||||
var parentType = declaringType.DeclaringType;
|
||||
if (parentType == null)
|
||||
|
216
src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs
Normal file
216
src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs
Normal file
@ -0,0 +1,216 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
internal static class TypeNameHelper
|
||||
{
|
||||
// from https://github.com/benaadams/Ben.Demystifier/blob/main/src/Ben.Demystifier/TypeNameHelper.cs
|
||||
// which was adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
|
||||
public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> FSharpTypeNames = new Dictionary<string, string>
|
||||
{
|
||||
{ "Unit", "void" },
|
||||
{ "FSharpOption", "Option" },
|
||||
{ "FSharpAsync", "Async" },
|
||||
{ "FSharpOption`1", "Option" },
|
||||
{ "FSharpAsync`1", "Async" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Pretty print a type name.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/>.</param>
|
||||
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
|
||||
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
|
||||
/// <returns>The pretty printed type name.</returns>
|
||||
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true,
|
||||
bool includeGenericParameterNames = false)
|
||||
{
|
||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name of given generic type without '`'.
|
||||
/// </summary>
|
||||
public static string GetTypeNameForGenericType(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
throw new ArgumentException("The given type should be generic", nameof(type));
|
||||
}
|
||||
|
||||
var genericPartIndex = type.Name.IndexOf('`');
|
||||
|
||||
return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name;
|
||||
}
|
||||
|
||||
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType != null)
|
||||
{
|
||||
ProcessType(builder, underlyingType, options);
|
||||
builder.Append('?');
|
||||
}
|
||||
else
|
||||
{
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
|
||||
}
|
||||
}
|
||||
else if (type.IsArray)
|
||||
{
|
||||
ProcessArrayType(builder, type, options);
|
||||
}
|
||||
else if (BuiltInTypeNames.TryGetValue(type, out var builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
else if (type.Namespace == nameof(System))
|
||||
{
|
||||
builder.Append(type.Name);
|
||||
}
|
||||
else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
else if (type.IsGenericParameter)
|
||||
{
|
||||
if (options.IncludeGenericParameterNames)
|
||||
{
|
||||
builder.Append(type.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
{
|
||||
var innerType = type;
|
||||
while (innerType.IsArray)
|
||||
{
|
||||
if (innerType.GetElementType() is { } inner)
|
||||
{
|
||||
innerType = inner;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessType(builder, innerType, options);
|
||||
|
||||
while (type.IsArray)
|
||||
{
|
||||
builder.Append('[');
|
||||
builder.Append(',', type.GetArrayRank() - 1);
|
||||
builder.Append(']');
|
||||
if (type.GetElementType() is not { } elementType)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
type = elementType;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length,
|
||||
DisplayNameOptions options)
|
||||
{
|
||||
var offset = 0;
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
offset = type.DeclaringType.GetGenericArguments().Length;
|
||||
}
|
||||
|
||||
if (options.FullName)
|
||||
{
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
|
||||
builder.Append('+');
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(type.Namespace))
|
||||
{
|
||||
builder.Append(type.Namespace);
|
||||
builder.Append('.');
|
||||
}
|
||||
}
|
||||
|
||||
var genericPartIndex = type.Name.IndexOf('`');
|
||||
if (genericPartIndex <= 0)
|
||||
{
|
||||
builder.Append(type.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out var builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(type.Name, 0, genericPartIndex);
|
||||
}
|
||||
|
||||
builder.Append('<');
|
||||
for (var i = offset; i < length; i++)
|
||||
{
|
||||
ProcessType(builder, genericArguments[i], options);
|
||||
if (i + 1 == length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(',');
|
||||
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
|
||||
{
|
||||
builder.Append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append('>');
|
||||
}
|
||||
|
||||
private struct DisplayNameOptions
|
||||
{
|
||||
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
|
||||
{
|
||||
FullName = fullName;
|
||||
IncludeGenericParameterNames = includeGenericParameterNames;
|
||||
}
|
||||
|
||||
public bool FullName { get; }
|
||||
|
||||
public bool IncludeGenericParameterNames { get; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user