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.