diff --git a/src/Spectre.Console/Widgets/Exceptions/ExceptionConverter.cs b/src/Spectre.Console/Widgets/Exceptions/ExceptionConverter.cs deleted file mode 100644 index f392db1..0000000 --- a/src/Spectre.Console/Widgets/Exceptions/ExceptionConverter.cs +++ /dev/null @@ -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().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("", 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(); - } -} \ No newline at end of file diff --git a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs index 7f20094..4b7c076 100644 --- a/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs +++ b/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs @@ -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()) { 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(']'); diff --git a/src/Spectre.Console/Widgets/Exceptions/ExceptionInfo.cs b/src/Spectre.Console/Widgets/Exceptions/ExceptionInfo.cs deleted file mode 100644 index 63f7c52..0000000 --- a/src/Spectre.Console/Widgets/Exceptions/ExceptionInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Spectre.Console; - -internal sealed class ExceptionInfo -{ - public string Type { get; } - public string Message { get; } - public List Frames { get; } - public ExceptionInfo? Inner { get; } - - public ExceptionInfo( - string type, string message, - List frames, - ExceptionInfo? inner) - { - Type = type ?? string.Empty; - Message = message ?? string.Empty; - Frames = frames ?? new List(); - Inner = inner; - } -} \ No newline at end of file diff --git a/src/Spectre.Console/Widgets/Exceptions/MethodExtensions.cs b/src/Spectre.Console/Widgets/Exceptions/MethodExtensions.cs new file mode 100644 index 0000000..ab31cea --- /dev/null +++ b/src/Spectre.Console/Widgets/Exceptions/MethodExtensions.cs @@ -0,0 +1,33 @@ +namespace Spectre.Console; + +internal static class MethodExtensions +{ + public static string GetName(this MethodBase? method) + { + if (method is null) + { + return ""; + } + + 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(); + } +} \ No newline at end of file diff --git a/src/Spectre.Console/Widgets/Exceptions/StackFrameInfo.cs b/src/Spectre.Console/Widgets/Exceptions/StackFrameInfo.cs deleted file mode 100644 index 1247c34..0000000 --- a/src/Spectre.Console/Widgets/Exceptions/StackFrameInfo.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/src/Spectre.Console/Widgets/Exceptions/StringUriExtensions.cs b/src/Spectre.Console/Widgets/Exceptions/StringUriExtensions.cs new file mode 100644 index 0000000..d7b906e --- /dev/null +++ b/src/Spectre.Console/Widgets/Exceptions/StringUriExtensions.cs @@ -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; + } + } +} \ No newline at end of file