Merge pull request #1755 from 0xced/fix-generic-exception-formatting

This commit is contained in:
Patrik Svensson 2025-02-05 18:39:38 +01:00 committed by GitHub
commit c1eb94c1db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 51 additions and 23 deletions

View File

@ -33,11 +33,12 @@ internal static class ExceptionFormatter
{
var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0;
var exceptionType = ex.GetType();
var exceptionTypeFullName = exceptionType.FullName ?? exceptionType.Name;
var type = Emphasize(exceptionTypeFullName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings);
var exceptionTypeName = TypeNameHelper.GetTypeDisplayName(exceptionType, fullName: !shortenTypes, includeSystemNamespace: true);
var type = new StringBuilder();
Emphasize(type, exceptionTypeName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings, limit: '<');
var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]";
return new Markup(string.Concat(type, ": ", message));
return new Markup($"{type}: {message}");
}
private static Grid GetStackFrames(Exception ex, ExceptionSettings settings)
@ -101,7 +102,7 @@ internal static class ExceptionFormatter
builder.Append(' ');
}
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
Emphasize(builder, methodName, new[] { '.' }, styles.Method, shortenMethods, settings);
builder.AppendWithStyle(styles.Parenthesis, "(");
AppendParameters(builder, method, settings);
builder.AppendWithStyle(styles.Parenthesis, ")");
@ -168,7 +169,7 @@ internal static class ExceptionFormatter
void AppendPath()
{
var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0;
builder.Append(Emphasize(path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings));
Emphasize(builder, path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings);
}
if ((settings.Format & ExceptionFormats.ShowLinks) != 0)
@ -192,32 +193,25 @@ internal static class ExceptionFormatter
}
}
private static string Emphasize(string input, char[] separators, Style color, bool compact,
ExceptionSettings settings)
private static void Emphasize(StringBuilder builder, string input, char[] separators, Style color, bool compact,
ExceptionSettings settings, char? limit = null)
{
var builder = new StringBuilder();
var limitIndex = limit.HasValue ? input.IndexOf(limit.Value) : -1;
var type = input;
var index = type.LastIndexOfAny(separators);
var index = limitIndex != -1 ? input[..limitIndex].LastIndexOfAny(separators) : input.LastIndexOfAny(separators);
if (index != -1)
{
if (!compact)
{
builder.AppendWithStyle(
settings.Style.NonEmphasized,
type.Substring(0, index + 1));
builder.AppendWithStyle(settings.Style.NonEmphasized, input[..(index + 1)]);
}
builder.AppendWithStyle(
color,
type.Substring(index + 1, type.Length - index - 1));
builder.AppendWithStyle(color, input[(index + 1)..]);
}
else
{
builder.Append(type.EscapeMarkup());
builder.AppendWithStyle(color, input);
}
return builder.ToString();
}
private static bool ShowInStackTrace(StackFrame frame)

View File

@ -39,11 +39,12 @@ internal static class TypeNameHelper
/// <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>
/// <param name="includeSystemNamespace"><c>true</c> to include the <c>System</c> namespace.</param>
/// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true)
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true, bool includeSystemNamespace = false)
{
var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeSystemNamespace));
return builder.ToString();
}
@ -71,7 +72,7 @@ internal static class TypeNameHelper
{
builder.Append(builtInName);
}
else if (type.Namespace == nameof(System))
else if (type.Namespace == nameof(System) && !options.IncludeSystemNamespace)
{
builder.Append(type.Name);
}
@ -181,14 +182,17 @@ internal static class TypeNameHelper
private struct DisplayNameOptions
{
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeSystemNamespace)
{
FullName = fullName;
IncludeGenericParameterNames = includeGenericParameterNames;
IncludeSystemNamespace = includeSystemNamespace;
}
public bool FullName { get; }
public bool IncludeGenericParameterNames { get; }
public bool IncludeSystemNamespace { get; }
}
}

View File

@ -6,6 +6,8 @@ public static class TestExceptions
public static bool GenericMethodThatThrows<T0, T1, TRet>(int? number) => throw new InvalidOperationException("Throwing!");
public static bool MethodThatThrowsGenericException<T>() => throw new GenericException<T>("Throwing!", default);
public static void ThrowWithInnerException()
{
try
@ -42,3 +44,6 @@ public static class TestExceptions
return ("key", new List<T>());
}
}
#pragma warning disable CS9113 // Parameter is unread.
public class GenericException<T>(string message, T value) : Exception(message);

View File

@ -0,0 +1,4 @@
Spectre.Console.Tests.Data.GenericException<Spectre.Console.IAnsiConsole>: Throwing!
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException<T>() in {ProjectDirectory}Data/Exceptions.cs:9
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_GenericException>b__8_0() in {ProjectDirectory}Unit/ExceptionTests.cs:134
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in {ProjectDirectory}Unit/ExceptionTests.cs:147

View File

@ -0,0 +1,4 @@
GenericException<IAnsiConsole>: Throwing!
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException<T>() in {ProjectDirectory}Data/Exceptions.cs:9
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_GenericException>b__8_0() in {ProjectDirectory}Unit/ExceptionTests.cs:134
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in {ProjectDirectory}Unit/ExceptionTests.cs:147

View File

@ -123,6 +123,23 @@ public sealed class ExceptionTests
return Verifier.Verify(result);
}
[Theory]
[InlineData(ExceptionFormats.Default)]
[InlineData(ExceptionFormats.ShortenTypes)]
[Expectation("GenericException")]
public Task Should_Write_GenericException(ExceptionFormats exceptionFormats)
{
// Given
var console = new TestConsole { EmitAnsiSequences = true }.Width(1024);
var dex = GetException(() => TestExceptions.MethodThatThrowsGenericException<IAnsiConsole>());
// When
var result = console.WriteNormalizedException(dex, exceptionFormats);
// Then
return Verifier.Verify(result).UseParameters(exceptionFormats);
}
public static Exception GetException(Action action)
{
try