diff --git a/src/Spectre.Console.Tests/Unit/Widgets/MarkupTests.cs b/src/Spectre.Console.Tests/Unit/Widgets/MarkupTests.cs index 0c4c8c3e0..0b629c25a 100644 --- a/src/Spectre.Console.Tests/Unit/Widgets/MarkupTests.cs +++ b/src/Spectre.Console.Tests/Unit/Widgets/MarkupTests.cs @@ -174,4 +174,51 @@ public void Can_Use_Interpolated_Markup_As_IRenderable() └─────────────────┘ ".NormalizeLineEndings()); } -} \ No newline at end of file + [Fact] + public void MarkupInterpolated_Should_Not_Throw_When_Object_ToString_Contains_Brackets() + { + // Given + var console = new TestConsole(); + var obj = new CoolThing(); + + // When + Exception ex = Record.Exception(() => console.MarkupInterpolated($"This is a {obj}")); + + // Then + ex.ShouldBeNull(); + console.Output.NormalizeLineEndings().ShouldBe("This is a This[contains, braces]."); + } + + [Fact] + public void MarkupInterpolated_Should_Preserve_Null_For_Custom_Formatter() + { + // Given + var console = new TestConsole(); + string? value = null; + + // When + Exception ex = Record.Exception(() => console.MarkupInterpolated(new NullAwareFormatProvider(), $"Value: {value}")); + + // Then + ex.ShouldBeNull(); + console.Output.NormalizeLineEndings().ShouldBe("Value: "); + } + + private class CoolThing + { + public override string ToString() => "This[contains, braces]."; + } + + private sealed class NullAwareFormatProvider : IFormatProvider, ICustomFormatter + { + public object? GetFormat(Type? formatType) + { + return formatType == typeof(ICustomFormatter) ? this : null; + } + + public string Format(string? format, object? arg, IFormatProvider? formatProvider) + { + return arg is null ? "" : arg.ToString() ?? string.Empty; + } + } +} diff --git a/src/Spectre.Console/Widgets/Markup.cs b/src/Spectre.Console/Widgets/Markup.cs index 15699904e..d0fbdb363 100644 --- a/src/Spectre.Console/Widgets/Markup.cs +++ b/src/Spectre.Console/Widgets/Markup.cs @@ -107,7 +107,15 @@ public static string Remove(string text) internal static string EscapeInterpolated(IFormatProvider provider, FormattableString value) { - object?[] args = value.GetArguments().Select(arg => arg is string s ? s.EscapeMarkup() : arg).ToArray(); + object?[] args = value.GetArguments() + .Select(arg => arg switch + { + null => null, + string text => text.EscapeMarkup(), + IFormattable => arg, + _ => arg.ToString()?.EscapeMarkup(), + }) + .ToArray(); return string.Format(provider, value.Format, args); } -} \ No newline at end of file +}