Skip to content

Commit c1eb94c

Browse files
Merge pull request #1755 from 0xced/fix-generic-exception-formatting
2 parents a6b96e9 + 9d8d3c1 commit c1eb94c

File tree

6 files changed

+51
-23
lines changed

6 files changed

+51
-23
lines changed

src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs

+13-19
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ private static Markup GetMessage(Exception ex, ExceptionSettings settings)
3333
{
3434
var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0;
3535
var exceptionType = ex.GetType();
36-
var exceptionTypeFullName = exceptionType.FullName ?? exceptionType.Name;
37-
var type = Emphasize(exceptionTypeFullName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings);
36+
var exceptionTypeName = TypeNameHelper.GetTypeDisplayName(exceptionType, fullName: !shortenTypes, includeSystemNamespace: true);
37+
var type = new StringBuilder();
38+
Emphasize(type, exceptionTypeName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings, limit: '<');
3839

3940
var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]";
40-
return new Markup(string.Concat(type, ": ", message));
41+
return new Markup($"{type}: {message}");
4142
}
4243

4344
private static Grid GetStackFrames(Exception ex, ExceptionSettings settings)
@@ -101,7 +102,7 @@ private static Grid GetStackFrames(Exception ex, ExceptionSettings settings)
101102
builder.Append(' ');
102103
}
103104

104-
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
105+
Emphasize(builder, methodName, new[] { '.' }, styles.Method, shortenMethods, settings);
105106
builder.AppendWithStyle(styles.Parenthesis, "(");
106107
AppendParameters(builder, method, settings);
107108
builder.AppendWithStyle(styles.Parenthesis, ")");
@@ -168,7 +169,7 @@ private static void AppendPath(StringBuilder builder, string path, ExceptionSett
168169
void AppendPath()
169170
{
170171
var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0;
171-
builder.Append(Emphasize(path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings));
172+
Emphasize(builder, path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings);
172173
}
173174

174175
if ((settings.Format & ExceptionFormats.ShowLinks) != 0)
@@ -192,32 +193,25 @@ void AppendPath()
192193
}
193194
}
194195

195-
private static string Emphasize(string input, char[] separators, Style color, bool compact,
196-
ExceptionSettings settings)
196+
private static void Emphasize(StringBuilder builder, string input, char[] separators, Style color, bool compact,
197+
ExceptionSettings settings, char? limit = null)
197198
{
198-
var builder = new StringBuilder();
199+
var limitIndex = limit.HasValue ? input.IndexOf(limit.Value) : -1;
199200

200-
var type = input;
201-
var index = type.LastIndexOfAny(separators);
201+
var index = limitIndex != -1 ? input[..limitIndex].LastIndexOfAny(separators) : input.LastIndexOfAny(separators);
202202
if (index != -1)
203203
{
204204
if (!compact)
205205
{
206-
builder.AppendWithStyle(
207-
settings.Style.NonEmphasized,
208-
type.Substring(0, index + 1));
206+
builder.AppendWithStyle(settings.Style.NonEmphasized, input[..(index + 1)]);
209207
}
210208

211-
builder.AppendWithStyle(
212-
color,
213-
type.Substring(index + 1, type.Length - index - 1));
209+
builder.AppendWithStyle(color, input[(index + 1)..]);
214210
}
215211
else
216212
{
217-
builder.Append(type.EscapeMarkup());
213+
builder.AppendWithStyle(color, input);
218214
}
219-
220-
return builder.ToString();
221215
}
222216

223217
private static bool ShowInStackTrace(StackFrame frame)

src/Spectre.Console/Widgets/Exceptions/TypeNameHelper.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ internal static class TypeNameHelper
3939
/// <param name="type">The <see cref="Type"/>.</param>
4040
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
4141
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
42+
/// <param name="includeSystemNamespace"><c>true</c> to include the <c>System</c> namespace.</param>
4243
/// <returns>The pretty printed type name.</returns>
43-
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true)
44+
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true, bool includeSystemNamespace = false)
4445
{
4546
var builder = new StringBuilder();
46-
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
47+
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeSystemNamespace));
4748
return builder.ToString();
4849
}
4950

@@ -71,7 +72,7 @@ private static void ProcessType(StringBuilder builder, Type type, DisplayNameOpt
7172
{
7273
builder.Append(builtInName);
7374
}
74-
else if (type.Namespace == nameof(System))
75+
else if (type.Namespace == nameof(System) && !options.IncludeSystemNamespace)
7576
{
7677
builder.Append(type.Name);
7778
}
@@ -181,14 +182,17 @@ private static void ProcessGenericType(StringBuilder builder, Type type, Type[]
181182

182183
private struct DisplayNameOptions
183184
{
184-
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
185+
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeSystemNamespace)
185186
{
186187
FullName = fullName;
187188
IncludeGenericParameterNames = includeGenericParameterNames;
189+
IncludeSystemNamespace = includeSystemNamespace;
188190
}
189191

190192
public bool FullName { get; }
191193

192194
public bool IncludeGenericParameterNames { get; }
195+
196+
public bool IncludeSystemNamespace { get; }
193197
}
194198
}

src/Tests/Spectre.Console.Tests/Data/Exceptions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public static class TestExceptions
66

77
public static bool GenericMethodThatThrows<T0, T1, TRet>(int? number) => throw new InvalidOperationException("Throwing!");
88

9+
public static bool MethodThatThrowsGenericException<T>() => throw new GenericException<T>("Throwing!", default);
10+
911
public static void ThrowWithInnerException()
1012
{
1113
try
@@ -42,3 +44,6 @@ public static (string Key, List<T> Values) GetTuplesWithInnerException<T>((int F
4244
return ("key", new List<T>());
4345
}
4446
}
47+
48+
#pragma warning disable CS9113 // Parameter is unread.
49+
public class GenericException<T>(string message, T value) : Exception(message);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Spectre.Console.Tests.Data.GenericException<Spectre.Console.IAnsiConsole>: Throwing!
2+
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException<T>() in {ProjectDirectory}Data/Exceptions.cs:9
3+
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_GenericException>b__8_0() in {ProjectDirectory}Unit/ExceptionTests.cs:134
4+
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in {ProjectDirectory}Unit/ExceptionTests.cs:147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
GenericException<IAnsiConsole>: Throwing!
2+
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrowsGenericException<T>() in {ProjectDirectory}Data/Exceptions.cs:9
3+
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_GenericException>b__8_0() in {ProjectDirectory}Unit/ExceptionTests.cs:134
4+
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in {ProjectDirectory}Unit/ExceptionTests.cs:147

src/Tests/Spectre.Console.Tests/Unit/ExceptionTests.cs

+17
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,23 @@ public Task Should_Write_Exception_With_No_StackTrace()
123123
return Verifier.Verify(result);
124124
}
125125

126+
[Theory]
127+
[InlineData(ExceptionFormats.Default)]
128+
[InlineData(ExceptionFormats.ShortenTypes)]
129+
[Expectation("GenericException")]
130+
public Task Should_Write_GenericException(ExceptionFormats exceptionFormats)
131+
{
132+
// Given
133+
var console = new TestConsole { EmitAnsiSequences = true }.Width(1024);
134+
var dex = GetException(() => TestExceptions.MethodThatThrowsGenericException<IAnsiConsole>());
135+
136+
// When
137+
var result = console.WriteNormalizedException(dex, exceptionFormats);
138+
139+
// Then
140+
return Verifier.Verify(result).UseParameters(exceptionFormats);
141+
}
142+
126143
public static Exception GetException(Action action)
127144
{
128145
try

0 commit comments

Comments
 (0)