diff --git a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs index f9fa7b8e8..0a946fe8d 100644 --- a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs +++ b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs @@ -33,11 +33,12 @@ private static Markup GetMessage(Exception ex, ExceptionSettings settings) { 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 @@ private static Grid GetStackFrames(Exception ex, ExceptionSettings settings) 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 @@ private static void AppendPath(StringBuilder builder, string path, ExceptionSett 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 @@ void AppendPath() } } - 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) diff --git a/src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs b/src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs index 2408c5506..eb0775534 100644 --- a/src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs +++ b/src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs @@ -39,11 +39,12 @@ internal static class TypeNameHelper /// The . /// true to print a fully qualified name. /// true to include generic parameter names. + /// true to include the System namespace. /// The pretty printed type name. - 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 @@ private static void ProcessType(StringBuilder builder, Type type, DisplayNameOpt { 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 @@ private static void ProcessGenericType(StringBuilder builder, Type type, Type[] 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; } } } \ No newline at end of file diff --git a/src/Tests/Spectre.Console.Tests/Data/Exceptions.cs b/src/Tests/Spectre.Console.Tests/Data/Exceptions.cs index b011a90b6..a7e066eee 100644 --- a/src/Tests/Spectre.Console.Tests/Data/Exceptions.cs +++ b/src/Tests/Spectre.Console.Tests/Data/Exceptions.cs @@ -6,6 +6,8 @@ public static class TestExceptions public static bool GenericMethodThatThrows(int? number) => throw new InvalidOperationException("Throwing!"); + public static bool MethodThatThrowsGenericException() => throw new GenericException("Throwing!", default); + public static void ThrowWithInnerException() { try @@ -42,3 +44,6 @@ public static (string Key, List Values) GetTuplesWithInnerException((int F return ("key", new List()); } } + +#pragma warning disable CS9113 // Parameter is unread. +public class GenericException(string message, T value) : Exception(message); diff --git a/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=Default.verified.txt b/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=Default.verified.txt new file mode 100644 index 000000000..08cbf0782 --- /dev/null +++ b/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=Default.verified.txt @@ -0,0 +1,4 @@ +Spectre.Console.Tests.Data.GenericException: Throwing! + at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException() in {ProjectDirectory}Data/Exceptions.cs:9 + at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.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 diff --git a/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=ShortenTypes.verified.txt b/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=ShortenTypes.verified.txt new file mode 100644 index 000000000..8da66ac38 --- /dev/null +++ b/src/Tests/Spectre.Console.Tests/Expectations/Exception/GenericException.Output_exceptionFormats=ShortenTypes.verified.txt @@ -0,0 +1,4 @@ +GenericException: Throwing! + at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException() in {ProjectDirectory}Data/Exceptions.cs:9 + at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.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 diff --git a/src/Tests/Spectre.Console.Tests/Unit/ExceptionTests.cs b/src/Tests/Spectre.Console.Tests/Unit/ExceptionTests.cs index a8eb2586a..2bc90de56 100644 --- a/src/Tests/Spectre.Console.Tests/Unit/ExceptionTests.cs +++ b/src/Tests/Spectre.Console.Tests/Unit/ExceptionTests.cs @@ -123,6 +123,23 @@ public Task Should_Write_Exception_With_No_StackTrace() 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()); + + // When + var result = console.WriteNormalizedException(dex, exceptionFormats); + + // Then + return Verifier.Verify(result).UseParameters(exceptionFormats); + } + public static Exception GetException(Action action) { try