Simplify exception formatting

Now that exceptions are not _parsed_ anymore (#513 and #637), keeping the `ExceptionInfo` and `StackFrameInfo` internal classes doesn't make much sense anymore. The `Exception` and `StackFrame` types can be used by the `ExceptionFormatter` class directly, without conversion.
This commit is contained in:
Cédric Luthi 2022-01-19 20:31:45 +01:00 committed by Phil Scott
parent f221c1f25c
commit ff4215f431
6 changed files with 104 additions and 166 deletions

View File

@ -1,57 +0,0 @@
namespace Spectre.Console;
internal static class ExceptionConverter
{
public static ExceptionInfo Convert(Exception exception)
{
if (exception is null)
{
throw new ArgumentNullException(nameof(exception));
}
var exceptionType = exception.GetType();
var stackTrace = new StackTrace(exception, true);
var frames = stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>().Select(Convert).ToList();
var inner = exception.InnerException is null ? null : Convert(exception.InnerException);
return new ExceptionInfo(exceptionType.FullName ?? exceptionType.Name, exception.Message, frames, inner);
}
private static StackFrameInfo Convert(StackFrame frame)
{
var method = frame.GetMethod();
if (method is null)
{
return new StackFrameInfo("<unknown method>", new List<(string Type, string Name)>(), null, null);
}
var methodName = GetMethodName(method);
var parameters = method.GetParameters().Select(e => (e.ParameterType.Name, e.Name ?? string.Empty)).ToList();
var path = frame.GetFileName();
var lineNumber = frame.GetFileLineNumber();
return new StackFrameInfo(methodName, parameters, path, lineNumber == 0 ? null : lineNumber);
}
private static string GetMethodName(MethodBase method)
{
var builder = new StringBuilder(256);
var fullName = method.DeclaringType?.FullName;
if (fullName != null)
{
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253
builder.Append(fullName.Replace('+', '.'));
builder.Append('.');
}
builder.Append(method.Name);
if (method.IsGenericMethod)
{
builder.Append('[');
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
builder.Append(']');
}
return builder.ToString();
}
}

View File

@ -9,35 +9,35 @@ internal static class ExceptionFormatter
throw new ArgumentNullException(nameof(exception));
}
var info = ExceptionConverter.Convert(exception);
return GetException(info, settings);
return GetException(exception, settings);
}
private static IRenderable GetException(ExceptionInfo info, ExceptionSettings settings)
private static IRenderable GetException(Exception exception, ExceptionSettings settings)
{
if (info is null)
if (exception is null)
{
throw new ArgumentNullException(nameof(info));
throw new ArgumentNullException(nameof(exception));
}
return new Rows(new IRenderable[]
{
GetMessage(info, settings),
GetStackFrames(info, settings),
GetMessage(exception, settings),
GetStackFrames(exception, settings),
}).Expand();
}
private static Markup GetMessage(ExceptionInfo ex, ExceptionSettings settings)
private static Markup GetMessage(Exception ex, ExceptionSettings settings)
{
var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0;
var type = Emphasize(ex.Type, new[] { '.' }, settings.Style.Exception, shortenTypes, settings);
var exceptionType = ex.GetType();
var exceptionTypeFullName = exceptionType.FullName ?? exceptionType.Name;
var type = Emphasize(exceptionTypeFullName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings);
var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]";
return new Markup(string.Concat(type, ": ", message));
}
private static Grid GetStackFrames(ExceptionInfo ex, ExceptionSettings settings)
private static Grid GetStackFrames(Exception ex, ExceptionSettings settings)
{
var styles = settings.Style;
@ -46,39 +46,44 @@ internal static class ExceptionFormatter
grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0));
// Inner
if (ex.Inner != null)
if (ex.InnerException != null)
{
grid.AddRow(
Text.Empty,
GetException(ex.Inner, settings));
GetException(ex.InnerException, settings));
}
// Stack frames
foreach (var frame in ex.Frames)
var stackTrace = new StackTrace(ex, fNeedFileInfo: true);
foreach (var frame in stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>())
{
var builder = new StringBuilder();
// Method
var shortenMethods = (settings.Format & ExceptionFormats.ShortenMethods) != 0;
builder.Append(Emphasize(frame.Method, new[] { '.' }, styles.Method, shortenMethods, settings));
var method = frame.GetMethod();
var methodName = method.GetName();
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
builder.AppendWithStyle(styles.Parenthesis, "(");
AppendParameters(builder, frame, settings);
AppendParameters(builder, method, settings);
builder.AppendWithStyle(styles.Parenthesis, ")");
if (frame.Path != null)
var path = frame.GetFileName();
if (path != null)
{
builder.Append(' ');
builder.AppendWithStyle(styles.Dimmed, "in");
builder.Append(' ');
// Path
AppendPath(builder, frame, settings);
AppendPath(builder, path, settings);
// Line number
if (frame.LineNumber != null)
var lineNumber = frame.GetFileLineNumber();
if (lineNumber != 0)
{
builder.AppendWithStyle(styles.Dimmed, ":");
builder.AppendWithStyle(styles.LineNumber, frame.LineNumber);
builder.AppendWithStyle(styles.LineNumber, lineNumber);
}
}
@ -90,30 +95,28 @@ internal static class ExceptionFormatter
return grid;
}
private static void AppendParameters(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings)
private static void AppendParameters(StringBuilder builder, MethodBase? method, ExceptionSettings settings)
{
var typeColor = settings.Style.ParameterType.ToMarkup();
var nameColor = settings.Style.ParameterName.ToMarkup();
var parameters = frame.Parameters.Select(x => $"[{typeColor}]{x.Type.EscapeMarkup()}[/] [{nameColor}]{x.Name.EscapeMarkup()}[/]");
builder.Append(string.Join(", ", parameters));
var parameters = method?.GetParameters().Select(x => $"[{typeColor}]{x.ParameterType.Name.EscapeMarkup()}[/] [{nameColor}]{x.Name?.EscapeMarkup()}[/]");
if (parameters != null)
{
builder.Append(string.Join(", ", parameters));
}
}
private static void AppendPath(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings)
private static void AppendPath(StringBuilder builder, string path, ExceptionSettings settings)
{
if (frame?.Path is null)
{
return;
}
void AppendPath()
{
var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0;
builder.Append(Emphasize(frame.Path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings));
builder.Append(Emphasize(path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings));
}
if ((settings.Format & ExceptionFormats.ShowLinks) != 0)
{
var hasLink = frame.TryGetUri(out var uri);
var hasLink = path.TryGetUri(out var uri);
if (hasLink && uri != null)
{
builder.Append("[link=").Append(uri.AbsoluteUri).Append(']');

View File

@ -1,20 +0,0 @@
namespace Spectre.Console;
internal sealed class ExceptionInfo
{
public string Type { get; }
public string Message { get; }
public List<StackFrameInfo> Frames { get; }
public ExceptionInfo? Inner { get; }
public ExceptionInfo(
string type, string message,
List<StackFrameInfo> frames,
ExceptionInfo? inner)
{
Type = type ?? string.Empty;
Message = message ?? string.Empty;
Frames = frames ?? new List<StackFrameInfo>();
Inner = inner;
}
}

View File

@ -0,0 +1,33 @@
namespace Spectre.Console;
internal static class MethodExtensions
{
public static string GetName(this MethodBase? method)
{
if (method is null)
{
return "<unknown method>";
}
var builder = new StringBuilder(256);
var fullName = method.DeclaringType?.FullName;
if (fullName != null)
{
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253
builder.Append(fullName.Replace('+', '.'));
builder.Append('.');
}
builder.Append(method.Name);
if (method.IsGenericMethod)
{
builder.Append('[');
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
builder.Append(']');
}
return builder.ToString();
}
}

View File

@ -1,58 +0,0 @@
namespace Spectre.Console;
internal sealed class StackFrameInfo
{
public string Method { get; }
public List<(string Type, string Name)> Parameters { get; }
public string? Path { get; }
public int? LineNumber { get; }
public StackFrameInfo(
string method, List<(string Type, string Name)> parameters,
string? path, int? lineNumber)
{
Method = method ?? throw new ArgumentNullException(nameof(method));
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
Path = path;
LineNumber = lineNumber;
}
public bool TryGetUri([NotNullWhen(true)] out Uri? result)
{
try
{
if (Path == null)
{
result = null;
return false;
}
if (!Uri.TryCreate(Path, UriKind.Absolute, out var uri))
{
result = null;
return false;
}
if (uri.Scheme == "file")
{
// For local files, we need to append
// the host name. Otherwise the terminal
// will most probably not allow it.
var builder = new UriBuilder(uri)
{
Host = Dns.GetHostName(),
};
uri = builder.Uri;
}
result = uri;
return true;
}
catch
{
result = null;
return false;
}
}
}

View File

@ -0,0 +1,37 @@
namespace Spectre.Console;
internal static class StringUriExtensions
{
public static bool TryGetUri(this string path, [NotNullWhen(true)] out Uri? result)
{
try
{
if (!Uri.TryCreate(path, UriKind.Absolute, out var uri))
{
result = null;
return false;
}
if (uri.Scheme == "file")
{
// For local files, we need to append
// the host name. Otherwise the terminal
// will most probably not allow it.
var builder = new UriBuilder(uri)
{
Host = Dns.GetHostName(),
};
uri = builder.Uri;
}
result = uri;
return true;
}
catch
{
result = null;
return false;
}
}
}