Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CodeBlocker.Test/DocCommentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
12 changes: 8 additions & 4 deletions CodeBlocker/Templates/DocComment.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2023-2026 ktsu-dev contributors
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.CodeBlocker.Templates;

Expand Down Expand Up @@ -221,13 +221,17 @@ private void WriteNamedElement(CodeBlocker codeBlocker, string tagName, string?
private string Escape(string text) => EscapeText ? EscapeContent(text) : text;

/// <summary>
/// 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 <c>///</c> 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.
/// </summary>
/// <param name="text">The text to split.</param>
/// <returns>The text's lines.</returns>
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');

/// <summary>
/// Escapes the characters that would otherwise be read as markup in element content.
Expand Down
Loading