βKeep output formats and protocols distinct
The JSON formatter converts a diagnostic into a string. The protocol layer handles LSP message structures, position conventions, and transport framing. For example, LSP 3.17's base protocol requires a Content-Length header before the JSON-RPC content, so an LSP connection must send messages under that transport contract.[Footnote 22] Giving connection and protocol management to a strategy that replaces one string-output step calls for checking whether it still has a common role.
<a id="unicode-boundary-example"></a>
Appendix E. Unicode and the single-line boundary
In this defensive implementation, "single-line" means no actual CR or LF. The human-readable formatter preserves NEL (U+0085), line separator (U+2028), and paragraph separator (U+2029). A display that treats those as line breaks needs broader validation. Sanitizing terminal control characters generally is also a separate output-boundary responsibility.
IsNullOrWhiteSpace rejects a message consisting only of these separators or tabs, but preserves them among other content. It does not trim leading or trailing spaces. The current validation also allows NUL; consumers must define the complete permitted character sets for file names and codes.
Input validation rejects whitespace-only values, while the output-shape check uses IsNullOrEmpty and accepts one space. Whether that is a meaningful diagnostic is a separate question.
The current constructor also stores unpaired surrogates, as in "Error \uD800". The .NET 8 default JSON path replaces the unpaired part with U+FFFD, so recovering the original assumes valid UTF-16 input. The tests below check replacement of unpaired high and low surrogates and preservation of a valid pair.[Footnote 13]
The verifier generates ExtendedProgram.Main() and calls UnicodeBoundaryExamples.Run() after Program.Main(). If you collect the code from Appendices A through C and the block below yourself, add UnicodeBoundaryExamples.Run(); inside Program.Main(). Without that call, only Appendix C's 38 checks run.
public static class UnicodeBoundaryExamples
{
public static void Run()
{
var checks = 0;
// These characters alone fail the whitespace-only input check.
foreach (var separator in new[] { "\u0085", "\u2028", "\u2029" })
{
var rejected = false;
try
{
_ = new Diagnostic("main.pgy", 1, "E001", separator);
}
catch (ArgumentException error) when (error.GetType() == typeof(ArgumentException))
{
rejected = true;
}
if (!rejected)
throw new InvalidOperationException("Whitespace-only messages must be rejected.");
checks++;
}
// The human-readable formatter preserves these separators among other content.
var message = "Before\u0085Middle\u2028Next\u2029After";
var diagnostic = new Diagnostic("main.pgy", 1, "E001", message);
var humanText = new HumanDiagnosticFormatter().Format(diagnostic);
foreach (var separator in new[] { '\u0085', '\u2028', '\u2029' })
{
if (!humanText.Contains(separator))
throw new InvalidOperationException("Separator preservation changed.");
checks++;
}
if (humanText.Contains('\r') || humanText.Contains('\n'))
throw new InvalidOperationException("This example's single-line contract prohibits CR/LF.");
checks++;
using (var parsed = JsonDocument.Parse(new JsonDiagnosticFormatter().Format(diagnostic)))
{
if (parsed.RootElement.GetProperty("message").GetString() != message)
throw new InvalidOperationException("The JSON message did not round-trip.");
checks++;
}
// Check that the constructor's current validation does not reject NUL.
var withNul = new Diagnostic("main\0.pgy", 1, "E001", "Error");
if (!withNul.File.Contains('\0'))
throw new InvalidOperationException("The NUL boundary changed.");
checks++;
// A single space passes the shape check without proving the diagnostic is meaningful.
using var output = new StringWriter(CultureInfo.InvariantCulture);
new DiagnosticReporter(new WhitespaceFormatter()).Report(diagnostic, output);
if (output.ToString() != " " + output.NewLine)
throw new InvalidOperationException("The whitespace-output shape check changed.");
checks++;
// Malformed UTF-16 accepted by the constructor can change during JSON serialization.
foreach (var example in new[]
{
(Input: "Error \uD800", Restored: "Error \uFFFD"),
(Input: "Error \uDC00", Restored: "Error \uFFFD"),
(Input: "Error \uD83D\uDE00", Restored: "Error \uD83D\uDE00")
})
{
var item = new Diagnostic("main.pgy", 1, "E001", example.Input);
if (item.Message != example.Input)
throw new InvalidOperationException("The constructor retains the original UTF-16 value.");
checks++;
using var parsed = JsonDocument.Parse(new JsonDiagnosticFormatter().Format(item));
if (parsed.RootElement.GetProperty("message").GetString() != example.Restored)
throw new InvalidOperationException("The UTF-16 serialization boundary changed.");
checks++;
}
Console.WriteLine($"C# Unicode boundary checks: {checks}");
}
private sealed class WhitespaceFormatter : IDiagnosticFormatter
{
public string Format(Diagnostic diagnostic) => " ";
}
}