diff --git a/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs b/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs
index 309489f7130..fa017c5ea0a 100644
--- a/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs
+++ b/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs
@@ -25,7 +25,7 @@ private static string GetMarkdownText(XElement elem)
foreach (var node in nodes)
{
if (node.NeedEmptyLineBefore())
- node.EnsureEmptyLineBefore();
+ node.EnsureEmptyLineBefore(node.Parent != elem);
if (node.NeedEmptyLineAfter())
node.EnsureEmptyLineAfter();
@@ -262,8 +262,8 @@ public static XElement[] GetBlockTags(this XElement elem)
public static bool NeedEmptyLineBefore(this XElement node)
=> NeedEmptyLine(node, Direction.Before);
- public static void EnsureEmptyLineBefore(this XElement node)
- => EnsureEmptyLine(node, Direction.Before);
+ public static void EnsureEmptyLineBefore(this XElement node, bool nested)
+ => EnsureEmptyLine(node, Direction.Before, nested);
public static bool NeedEmptyLineAfter(this XElement node)
=> NeedEmptyLine(node, Direction.After);
@@ -296,7 +296,7 @@ private static bool NeedEmptyLine(this XElement node, Direction direction)
return NeedEmptyLineRules.TryGetValue((leftKind, rightKind), out var result) && result;
}
- private static void EnsureEmptyLine(this XElement node, Direction direction)
+ private static void EnsureEmptyLine(this XElement node, Direction direction, bool nested = false)
{
var adjacentNode = node.GetAdjacentNode(direction);
@@ -329,7 +329,12 @@ private static void EnsureEmptyLine(this XElement node, Direction direction)
return;
}
- textNode.Value = textNode.Value.Insert(insertIndex, $"{newLineChars}{indent}");
+ // A new blank line ends the containing HTML block. Align the nested
+ // tag with its container so its XML indentation cannot become code.
+ if (nested && direction == Direction.Before && !textNode.IsWhitespaceNode())
+ textNode.Value = textNode.Value[..insertIndex] + newLineChars + GetLineIndent(node.Parent!);
+ else
+ textNode.Value = textNode.Value.Insert(insertIndex, $"{newLineChars}{indent}");
return;
default:
@@ -455,6 +460,11 @@ private static string GetIndentToInsert(XElement node, Direction direction)
if (node.TryGetCurrentIndent(direction, out _))
return "";
+ return GetLineIndent(node);
+ }
+
+ private static string GetLineIndent(XElement node)
+ {
// Inline children inherit the indentation of the line containing their parent.
for (XElement? current = node; current != null; current = current.Parent)
{
diff --git a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Blocks.cs b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Blocks.cs
new file mode 100644
index 00000000000..c17afc4fc92
--- /dev/null
+++ b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Blocks.cs
@@ -0,0 +1,219 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Markdig;
+using Xunit;
+
+namespace Docfx.Dotnet.Tests;
+
+public partial class XmlCommentSummaryTest
+{
+ [Theory]
+ [InlineData(" ", "Indented")]
+ [InlineData("", "Non-indented")]
+ public void Blocks_List_Issue11173(string indent, string description)
+ {
+ var summary = XmlComment.Parse($"""
+
+ {indent}
+
", html);
+ }
+
+ [Theory]
+ [InlineData("bullet", "ul")]
+ [InlineData("number", "ol")]
+ [InlineData("table", "table")]
+ public void Blocks_OnlyListIsIndented(string type, string tag)
+ {
+ var summary = XmlComment.Parse($"""
+
+
+ Unindented text before an indented list:
+
+ see .
+ second item.
+
+ Following **markdown**.
+
+
+ """).Summary;
+
+ var html = Markdown.ToHtml(summary);
+
+ Assert.Contains($"<{tag}>", html);
+ Assert.Contains("Following markdown.", html);
+ Assert.DoesNotContain("", html);
+ }
+
+ [Fact]
+ public void Blocks_NestedListsWithInlineReferences()
+ {
+ var summary = XmlComment.Parse("""
+
+
+ See :
+
+ -
+ First item with
:
+
+ - Nested item.
+
+
+ - Second item.
+
+
+
+ """).Summary;
+
+ var html = Markdown.ToHtml(summary);
+
+ Assert.Contains("- ", html);
+ Assert.Contains("
- Nested item.
", html);
+ Assert.Contains(" - Second item.
", html);
+ Assert.Contains("", html);
+ }
+
+ [Theory]
+ [InlineData("{0} ")]
+ [InlineData("{0}")]
+ [InlineData("{0} ")]
+ [InlineData("{0}
")]
+ [InlineData("{0}
")]
+ public void Blocks_NewSeparatorPreservesNestedXml(string container)
+ {
+ foreach (var rootIndent in new[] { "", " " })
+ foreach (var indent in new[] { "", " ", " ", " ", "\t" })
+ foreach (var separator in new[] { "", "\n" })
+ {
+ var content = $"\n{rootIndent}{indent}A list:{separator}{rootIndent}{indent}- see
.
\n{rootIndent}";
+ var input = $"\n{rootIndent}Before **bold**.\n\n{rootIndent}{string.Format(container, content)}\n\n{rootIndent}After **bold**.\n ";
+
+ var html = Markdown.ToHtml(XmlComment.Parse(input).Summary);
+
+ Assert.Contains("- see
Before bold.", html);
+ Assert.Contains("After bold.
", html);
+ Assert.DoesNotContain("", html);
+ }
+ }
+
+ [Theory]
+ [InlineData("bullet", "ul")]
+ [InlineData("number", "ol")]
+ [InlineData("table", "table")]
+ public void Blocks_NewSeparatorPreservesFollowingTextIndent(string type, string tag)
+ {
+ var summary = XmlComment.Parse($$""""
+
+
+ See :
+ Item.
+ IndentedCode();
+
+ Following and **bold**.
+
+ ```csharp
+ if (ready)
+ Run();
+ ```
+
+ if (ready)
+ {
+
+ Run();
+ }
+
+
+
+ """").Summary;
+
+ var html = Markdown.ToHtml(summary);
+
+ Assert.Contains($"<{tag}>", html);
+ Assert.Contains("IndentedCode();\n
", html);
+ Assert.Contains("and bold.", html);
+ Assert.Contains("if (ready)\n Run();\n
", html);
+ Assert.Contains("if (ready)\n{\n\n Run();\n}
", html);
+ Assert.DoesNotContain($"<{tag}>", html);
+ }
+
+ [Fact]
+ public void Blocks_NewSeparatorOnlyChangesTagIndent()
+ {
+ ValidateSummary(
+ """
+
+
+ A list:
+ - Item.
+ Code();
+
+
+ """,
+ """
+
+ A list:
+
+
- Item.
+
+ Code();
+
+ """);
+ }
+
+ [Fact]
+ public void Blocks_ExistingBlankLineKeepsIndent()
+ {
+ ValidateSummary(
+ """
+
+
+ Description.
+
+ - Item.
+
+
+ """,
+ """
+
+ Description.
+
+
- Item.
+
+ """);
+ }
+
+ [Fact]
+ public void Blocks_TopLevelMarkdownKeepsIndent()
+ {
+ ValidateSummary(
+ """
+
+ Description.
+ - Item.
+
+ """,
+ """
+ Description.
+
+ - Item.
+ """);
+ }
+}