From ac06caa09a09255fc1323196d428646de778f789 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 15:26:18 +0000 Subject: [PATCH] Start a new doc comment line at NEL, LINE SEPARATOR and PARAGRAPH SEPARATOR [patch] The compiler ends a /// comment at U+0085, U+2028 and U+2029 as well as at CR and LF. DocComment.SplitLines only split at CR and LF, so doc text holding one of the others pushed the rest of the line out of the comment and the generated file failed to compile. Fixes #129 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014h6btvAuM1Ew7cCFSpuFWc --- CodeBlocker.Test/DocCommentTests.cs | 17 +++++++++++++++++ CodeBlocker/Templates/DocComment.cs | 12 ++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CodeBlocker.Test/DocCommentTests.cs b/CodeBlocker.Test/DocCommentTests.cs index eabbbb2..f802433 100644 --- a/CodeBlocker.Test/DocCommentTests.cs +++ b/CodeBlocker.Test/DocCommentTests.cs @@ -74,6 +74,23 @@ public void EitherLineTerminatorSplitsTheText() Render(new DocComment { Summary = "a\r\nb" })); } + [TestMethod] + [DataRow("\u0085", DisplayName = "NEL")] + [DataRow("\u2028", DisplayName = "LINE SEPARATOR")] + [DataRow("\u2029", DisplayName = "PARAGRAPH SEPARATOR")] + public void EveryLineTerminatorTheCompilerRecognisesStartsANewCommentLine(string separator) + { + // The compiler ends a /// comment at each of these, so text after one left inside a line is + // parsed as code and the generated file does not compile. + string rendered = Render(new DocComment { Summary = $"Line one{separator}Line two" }); + + Assert.AreEqual(Render(new DocComment { Summary = "Line one\nLine two" }), rendered); + foreach (string line in rendered.Split('\n', '\r', '\u0085', '\u2028', '\u2029')) + { + Assert.IsTrue(line.Length == 0 || line.StartsWith("///", StringComparison.Ordinal), $"Not inside the comment: \"{line}\""); + } + } + [TestMethod] public void TextContentIsEscapedByDefault() { diff --git a/CodeBlocker/Templates/DocComment.cs b/CodeBlocker/Templates/DocComment.cs index 6e52b13..48902fa 100644 --- a/CodeBlocker/Templates/DocComment.cs +++ b/CodeBlocker/Templates/DocComment.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 ktsu-dev contributors +// Copyright (c) 2023-2026 ktsu-dev contributors namespace ktsu.CodeBlocker.Templates; @@ -221,13 +221,17 @@ private void WriteNamedElement(CodeBlocker codeBlocker, string tagName, string? private string Escape(string text) => EscapeText ? EscapeContent(text) : text; /// - /// Splits text into lines, accepting either line terminator so that a caller's verbatim or raw - /// string literal lays out the same way whichever platform the source file was written on. + /// Splits text into lines at every terminator the C# compiler recognises: either platform's + /// line ending, so that a caller's verbatim or raw string literal lays out the same way + /// whichever platform the source file was written on, and also NEL (U+0085), LINE SEPARATOR + /// (U+2028) and PARAGRAPH SEPARATOR (U+2029). The compiler ends a /// comment at any of + /// those, so left inside a line they would push the rest of the text out of the comment and + /// into the code. /// /// The text to split. /// The text's lines. private static string[] SplitLines(string text) => - text.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'); + text.Replace("\r\n", "\n").Split('\n', '\r', '\u0085', '\u2028', '\u2029'); /// /// Escapes the characters that would otherwise be read as markup in element content.