diff --git a/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs b/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs index c34fb0defd5..b0584d630b7 100644 --- a/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs +++ b/src/Docfx.Dotnet/Parsers/XmlComment.Extensions.cs @@ -1,49 +1,34 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; +using System.Text; using System.Xml; using System.Xml.Linq; -using System.Xml.XPath; + +#nullable enable namespace Docfx.Dotnet; internal partial class XmlComment { - // List of block tags that are defined by CommonMark - // https://spec.commonmark.org/0.31.2/#html-blocks - private static readonly string[] BlockTags = - { - "ol", - "p", - "table", - "ul", - - // Recommended XML tags for C# documentation comments - "example", - - // Other tags - "pre", - }; - - private static readonly Lazy BlockTagsXPath = new(string.Join(" | ", BlockTags.Select(tagName => $".//{tagName}"))); - /// /// Gets markdown text from XElement. /// private static string GetMarkdownText(XElement elem) { - // Gets HTML block tags by XPath. - var nodes = elem.XPathSelectElements(BlockTagsXPath.Value).ToArray(); + // Gets HTML block tags from tree. + var nodes = elem.GetBlockTags(); // Insert HTML/Markdown separator lines. foreach (var node in nodes) { if (node.NeedEmptyLineBefore()) - node.InsertEmptyLineBefore(); + node.EnsureEmptyLineBefore(); if (node.NeedEmptyLineAfter()) - node.AddAfterSelf(new XText("\n")); + node.EnsureEmptyLineAfter(); } return elem.GetInnerXml(); @@ -53,12 +38,9 @@ private static string GetInnerXml(XElement elem) => elem.GetInnerXml(); } -// Define file scoped extension methods. -static file class XElementExtensions +// Define file scoped extension methods for GetInnerXml. +static file class GetInnerXmlExtensions { - /// - /// Gets inner XML text of XElement. - /// public static string GetInnerXml(this XElement elem) { using var sw = new StringWriter(); @@ -83,6 +65,7 @@ public static string GetInnerXml(this XElement elem) xml = RemoveCommonIndent(xml); // Trim beginning spaces/lines if text starts with HTML tag. + // It is necessary to avoid it being handled as a markdown code block. var firstNode = nodes.FirstOrDefault(x => !x.IsWhitespaceNode()); if (firstNode != null && firstNode.NodeType == XmlNodeType.Element) xml = xml.TrimStart(); @@ -95,198 +78,494 @@ public static string GetInnerXml(this XElement elem) return xml; } - public static bool NeedEmptyLineBefore(this XElement node) + private static string RemoveCommonIndent(string text) { - if (!node.TryGetNonWhitespacePrevNode(out var prevNode)) - return false; + ReadOnlySpan span = text.AsSpan(); + + // 1st pass: Compute minimum indent (excluding
 blocks)
+        bool inPre = false;
+        int minIndent = int.MaxValue;
 
-        switch (prevNode.NodeType)
+        int pos = 0;
+        while (pos < span.Length)
         {
-            // If prev node is HTML element. No need to insert empty line.
-            case XmlNodeType.Element:
-                return false;
+            var line = ReadLine(span, ref pos);
+
+            if (!inPre && !IsWhitespaceLine(line))
+            {
+                int indent = CountIndent(line);
+                if (indent < minIndent)
+                    minIndent = indent;
+            }
 
-            // Ensure empty lines exists before text node.
-            case XmlNodeType.Text:
-                var prevTextNode = (XText)prevNode;
+            inPre = UpdatePreFlag(inPre, line);
+        }
 
-                // No need to insert line if prev node ends with empty line.
-                if (prevTextNode.Value.EndsWithEmptyLine())
-                    return false;
+        if (minIndent == int.MaxValue)
+            minIndent = 0;
 
-                return true;
+        // 2nd pass: build result
+        var sb = new StringBuilder(text.Length);
 
-            default:
+        inPre = false;
+        pos = 0;
+
+        while (pos < span.Length)
+        {
+            var line = ReadLine(span, ref pos);
+
+            if (!inPre && line.Length != 0)
+            {
+                int remove = Math.Min(minIndent, CountIndent(line));
+                sb.Append(line.Slice(remove));
+            }
+            else
+            {
+                sb.Append(line);
+            }
+
+            sb.Append('\n');
+
+            inPre = UpdatePreFlag(inPre, line);
+        }
+
+        // Ensure trailing newline
+        sb.Append('\n');
+
+        return sb.ToString();
+    }
+
+    private static int CountIndent(ReadOnlySpan line)
+    {
+        int i = 0;
+        while (i < line.Length && HelperMethods.IsIndentChar(line[i]))
+            i++;
+        return i;
+    }
+
+    private static bool UpdatePreFlag(bool inPre, ReadOnlySpan line)
+    {
+        var trimmed = line.Trim();
+
+        // Check start tag (It might contains attribute)
+        if (!inPre && trimmed.StartsWith("", StringComparison.OrdinalIgnoreCase))
+            inPre = false;
+
+        return inPre;
+    }
+
+    private static bool IsWhitespaceLine(ReadOnlySpan line)
+    {
+        foreach (var c in line)
+        {
+            if (!char.IsWhiteSpace(c))
                 return false;
         }
+        return true;
     }
 
-    public static void InsertEmptyLineBefore(this XElement elem)
+    private static ReadOnlySpan ReadLine(ReadOnlySpan text, ref int pos)
     {
-        if (!elem.TryGetNonWhitespacePrevNode(out var prevNode))
-            return;
+        int start = pos;
+        while (pos < text.Length && text[pos] != '\n')
+            ++pos;
+
+        int length = pos - start;
 
-        Debug.Assert(prevNode.NodeType == XmlNodeType.Text);
+        // skip '\n'
+        if (pos < text.Length && text[pos] == '\n')
+            ++pos;
 
-        var prevTextNode = (XText)prevNode;
-        var span = prevTextNode.Value.AsSpan();
-        int index = span.LastIndexOf('\n');
+        return text.Slice(start, length);
+    }
+}
 
-        ReadOnlySpan lastLine = index == -1
-            ? span
-            : span.Slice(index + 1);
+// Define file scoped extension methods for XNode/XElement.
+static file class XNodeExtensions
+{
+    /// 
+    /// The whole spacing rule is defined ONLY here.
+    /// Key = (left, right)
+    /// Value = need empty line between them
+    /// 
+    private static readonly Dictionary<(NodeKind prev, NodeKind next), bool> NeedEmptyLineRules = new()
+    {
+        //Block -> *
+        [(NodeKind.Block, NodeKind.Other)] = false,
+        [(NodeKind.Block, NodeKind.Block)] = false,
+        [(NodeKind.Block, NodeKind.Pre)] = true,
+        [(NodeKind.Block, NodeKind.Text)] = true,
+
+        // Pre -> *
+        [(NodeKind.Pre, NodeKind.Other)] = true,
+        [(NodeKind.Pre, NodeKind.Block)] = true,
+        [(NodeKind.Pre, NodeKind.Pre)] = false,
+        [(NodeKind.Pre, NodeKind.Text)] = true,
+
+        // Other -> *
+        [(NodeKind.Other, NodeKind.Block)] = false,
+        [(NodeKind.Other, NodeKind.Pre)] = true,
+        [(NodeKind.Other, NodeKind.Other)] = false,
+        [(NodeKind.Other, NodeKind.Text)] = true,
+
+        // Text -> *
+        [(NodeKind.Text, NodeKind.Block)] = true,
+        [(NodeKind.Text, NodeKind.Pre)] = true,
+        [(NodeKind.Text, NodeKind.Other)] = true,
+        [(NodeKind.Text, NodeKind.Text)] = false,
+    };
+
+    private static readonly HashSet BlockTags = new(StringComparer.OrdinalIgnoreCase)
+    {
+        "ol",
+        "p",
+        "table",
+        "ul",
+
+        // Recommended XML tags for C# documentation comments
+        // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/recommended-tags
+        // Note: Some XML tags(e.g. ``/``) are pre-processed and converted to HTML tags.
+        "example",
+        
+        // Other tags
+        "pre",
+    };
+
+    private enum NodeKind
+    {
+        // XElement
+        Block, // HTML element that requires empty line before/after tag.
+        Pre,   // 
 tag. It's handled same as block type. It require additional rules.
+        Other, // Other HTML tags
+
+        // XText
+        Text,
+    }
+
+    private enum Direction
+    {
+        Before,
+        After,
+    }
 
-        if (lastLine.Length > 0 && lastLine.IsWhiteSpace())
+    public static XElement[] GetBlockTags(this XElement elem)
+    {
+        return elem.Descendants()
+                   .Where(e => BlockTags.Contains(e.Name.LocalName))
+                   .ToArray();
+    }
+
+    public static bool NeedEmptyLineBefore(this XElement node)
+        => NeedEmptyLine(node, Direction.Before);
+
+    public static void EnsureEmptyLineBefore(this XElement node)
+        => EnsureEmptyLine(node, Direction.Before);
+
+    public static bool NeedEmptyLineAfter(this XElement node)
+        => NeedEmptyLine(node, Direction.After);
+
+    public static void EnsureEmptyLineAfter(this XElement node)
+        => EnsureEmptyLine(node, Direction.After);
+
+    private static bool NeedEmptyLine(this XElement node, Direction direction)
+    {
+        // Check whitespace text node.
+        XNode? neighborNode = FindNonWhitespaceNeighbor(node, direction);
+
+        if (neighborNode == null)
+            return false;
+
+        NodeKind leftKind;
+        NodeKind rightKind;
+
+        if (direction == Direction.Before)
         {
-            // Insert new line before indent of last line.
-            prevTextNode.Value = prevTextNode.Value.Insert(index, "\n");
+            leftKind = GetNodeKind(neighborNode);
+            rightKind = GetNodeKind(node);
         }
         else
         {
-            elem.AddBeforeSelf(new XText("\n"));
+            leftKind = GetNodeKind(node);
+            rightKind = GetNodeKind(neighborNode);
         }
+
+        return NeedEmptyLineRules.TryGetValue((leftKind, rightKind), out var result) && result;
     }
 
-    private static bool EndsWithEmptyLine(this ReadOnlySpan span)
+    private static void EnsureEmptyLine(this XElement node, Direction direction)
     {
-        var index = span.LastIndexOfAnyExcept([' ', '\t']);
-        if (index >= 0 && span[index] == '\n')
+        var adjacentNode = node.GetAdjacentNode(direction);
+
+        switch (adjacentNode)
         {
-            span = span.Slice(0, index);
-            index = span.LastIndexOfAnyExcept([' ', '\t']);
-            if (index >= 0 && span[index] == '\n')
-                return true;
+            case null:
+            case XElement:
+                if (direction == Direction.Before)
+                    node.AddBeforeSelf(new XText("\n\n"));
+                else
+                    node.AddAfterSelf(new XText("\n\n"));
+                return;
+
+            case XText textNode:
+                int count = textNode.CountConsecutiveNewLines(direction, out var insertIndex);
+                var indent = GetIndentToInsert(node, direction, insertIndex);
+
+                var newLineChars = count switch
+                {
+                    0 => "\n\n",
+                    1 => "\n",
+                    _ => "",
+                };
+
+                if (newLineChars == "")
+                {
+                    // It's not expected to be called. Because it's skipped by NeedEmptyLine check.
+                    Debug.Assert(textNode.HasEmptyLine(direction));
+                    return;
+                }
+
+                textNode.Value = textNode.Value.Insert(insertIndex, $"{newLineChars}{indent}");
+                return;
+
+            default:
+                return;
         }
+    }
+
+    private static NodeKind GetNodeKind(XNode node)
+    {
+        if (node is not XElement elem)
+            return NodeKind.Text;
 
-        return false;
+        if (elem.IsPreTag())
+            return NodeKind.Pre;
+
+        if (elem.IsBlockTag())
+            return NodeKind.Block;
+
+        return NodeKind.Other;
     }
 
-    private static bool TryGetNonWhitespacePrevNode(this XElement elem, out XNode result)
+    private static XNode? GetAdjacentNode(this XNode node, Direction direction)
     {
-        var prev = elem.PreviousNode;
-        while (prev != null && prev.IsWhitespaceNode())
-            prev = prev.PreviousNode;
+        return direction == Direction.Before
+            ? node.PreviousNode
+            : node.NextNode;
+    }
 
-        if (prev == null)
-        {
-            result = null;
-            return false;
-        }
+    private static XNode? FindNonWhitespaceNeighbor(this XNode node, Direction direction)
+    {
+        var current = node.GetAdjacentNode(direction);
 
-        result = prev;
-        return true;
+        while (current != null && current.IsWhitespaceNode())
+            current = current.GetAdjacentNode(direction);
+
+        // If node is not found. Use parent instead.
+        current ??= node.Parent;
+
+        return current;
     }
 
-    public static bool NeedEmptyLineAfter(this XElement node)
+    private static T? FindNeighbor(this XNode node, Direction direction)
+        where T : XNode
     {
-        if (!node.TryGetNonWhitespaceNextNode(out var nextNode))
-            return false;
+        var current = node.GetAdjacentNode(direction);
+
+        while (current != null && current is not T)
+            current = current.GetAdjacentNode(direction);
+
+        return (T?)current;
+    }
+
+    private static bool HasEmptyLine(this XText node, Direction direction)
+      => node.CountConsecutiveNewLines(direction, out _) >= 2;
+
+    /// 
+    /// Counts consecutive '\n' characters that exist before/after
+    /// 
+    /// 
+    /// Direction.Before scans from the end
+    /// Direction.After scans from the beginning.
+    /// 
+    /// 
+    /// The position where new content should be inserted.
+    /// It's determined from the first newline found.
+    /// 
+    /// 
+    /// The number of consecutive newline characters found.
+    /// 
+    private static int CountConsecutiveNewLines(this XText node, Direction direction, out int insertIndex)
+    {
+        var span = node.Value.AsSpan();
+        int count = 0;
 
-        switch (nextNode.NodeType)
+        switch (direction)
         {
-            // If next node is HTML element. No need to insert new line.
-            case XmlNodeType.Element:
-                return false;
+            case Direction.Before:
+                insertIndex = span.Length;
+                for (int i = span.Length - 1; i >= 0; --i)
+                {
+                    char c = span[i];
+
+                    if (HelperMethods.IsIndentChar(c))
+                        continue;
+
+                    if (c != '\n')
+                        break;
+
+                    if (count == 0)
+                        insertIndex = i + 1;
+
+                    count++;
+                }
+                return count;
+
+            case Direction.After:
+                insertIndex = 0;
+                for (int i = 0; i < span.Length; ++i)
+                {
+                    char c = span[i];
 
-            // Ensure empty lines exists after node.
-            case XmlNodeType.Text:
-                var nextTextNode = (XText)nextNode;
-                var textSpan = nextTextNode.Value.AsSpan();
+                    if (HelperMethods.IsIndentChar(c))
+                        continue;
 
-                // No need to insert line if prev node ends with empty line.
-                if (textSpan.StartsWithEmptyLine())
-                    return false;
+                    if (c != '\n')
+                        break;
 
-                return true;
+                    if (count == 0)
+                        insertIndex = i;
+
+                    count++;
+                }
+                return count;
 
             default:
-                return false;
+                throw new UnreachableException();
         }
     }
-    private static bool StartsWithEmptyLine(this ReadOnlySpan span)
+
+    private static string GetIndentToInsert(XElement node, Direction direction, int insertIndex)
     {
-        var index = span.IndexOfAnyExcept([' ', '\t']);
-        if (index >= 0 && span[index] == '\n')
-        {
-            ++index;
-            if (index > span.Length)
-                return false;
+        // Check whether there is an existing indent.
+        if (node.TryGetCurrentIndent(direction, out _))
+            return "";
 
-            span = span.Slice(index);
-            index = span.IndexOfAnyExcept([' ', '\t']);
+        // Try to get indent from text node that is placed before.
+        var beforeTextNode = node.FindNeighbor(Direction.Before);
+        if (beforeTextNode != null)
+            return beforeTextNode.GetIndentFromLastLine();
 
-            if (index < 0 || span[index] == '\n')
-                return true; // There is no content or empty line is already exists.
-        }
-        return false;
+        return "";
     }
 
-    private static bool TryGetNonWhitespaceNextNode(this XElement elem, out XNode result)
+    private static bool TryGetCurrentIndent(this XElement node, Direction direction, out string indent)
     {
-        var next = elem.NextNode;
-        while (next != null && next.IsWhitespaceNode())
-            next = next.NextNode;
+        indent = "";
+
+        var adjacentNode = node.GetAdjacentNode(direction);
+        if (adjacentNode == null || adjacentNode is not XText textNode)
+            return false;
 
-        if (next == null)
+        ReadOnlySpan result = direction switch
         {
-            result = null;
+            Direction.Before => GetIndentBefore(textNode.Value),
+            Direction.After => GetIndentAfter(textNode.Value),
+            _ => throw new UnreachableException()
+        };
+
+        if (result.IsEmpty)
             return false;
-        }
 
-        result = next;
+        indent = result.ToString();
         return true;
     }
 
-    private static string RemoveCommonIndent(string text)
+    private static string GetIndentBefore(ReadOnlySpan span)
     {
-        var lines = text.Split('\n').ToArray();
+        int lastNewLine = span.LastIndexOf('\n');
+        if (lastNewLine < 0)
+            return "";
+
+        var lastLineSpan = span[(lastNewLine + 1)..];
+        if (lastLineSpan.Length == 0)
+            return "";
 
-        var inPre = false;
-        var indentCounts = new List();
+        if (lastLineSpan.ContainsAnyExcept([' ', '\t']))
+            return "";
 
-        // Caluculate line's indent chars (
 tag region is excluded)
-        foreach (var line in lines)
+        return lastLineSpan.ToString();
+    }
+
+    private static string GetIndentAfter(ReadOnlySpan span)
+    {
+        int i = 0;
+
+        while (i < span.Length)
         {
-            if (!inPre && !string.IsNullOrWhiteSpace(line))
+            int lineStart = i;
+
+            int indentLength = span[i..].IndexOfAnyExcept([' ', '\t']);
+            if (indentLength < 0)
+                return "";
+
+            i += indentLength;
+            int indentEnd = i;
+
+            // Skip empty line
+            if (span[i] == '\n')
             {
-                int indent = line.TakeWhile(c => c == ' ' || c == '\t').Count();
-                indentCounts.Add(indent);
+                i++;
+                continue;
             }
 
-            var trimmed = line.Trim();
-            if (trimmed.StartsWith("", StringComparison.OrdinalIgnoreCase))
-                inPre = false;
+            // Return indent
+            return span.Slice(lineStart, indentEnd - lineStart).ToString();
         }
 
-        int minIndent = indentCounts.DefaultIfEmpty(0).Min();
+        return "";
+    }
 
-        inPre = false;
-        var resultLines = new List();
-        foreach (var line in lines)
-        {
-            if (!inPre && line.Length >= minIndent)
-                resultLines.Add(line.Substring(minIndent));
-            else
-                resultLines.Add(line);
-
-            // Update inPre flag.
-            var trimmed = line.Trim();
-            if (trimmed.StartsWith("
", StringComparison.OrdinalIgnoreCase))
-                inPre = true;
-            if (trimmed.EndsWith("
", StringComparison.OrdinalIgnoreCase)) - inPre = false; - } + private static string GetIndentFromLastLine(this XText textNode) + { + ReadOnlySpan text = textNode.Value; + + int lastNewLineIndex = text.LastIndexOf('\n'); + if (lastNewLineIndex < 0) + return ""; + + var line = text.Slice(lastNewLineIndex + 1); + + if (line.IsEmpty) + return ""; + + if (!line.ContainsAnyExcept([' ', '\t'])) + return line.ToString(); - // Insert empty line to append `\n`. - resultLines.Add(""); + int index = line.IndexOfAnyExcept([' ', '\t']); + if (index <= 0) + return ""; - return string.Join("\n", resultLines); + return line.Slice(0, index).ToString(); } - private static bool IsWhitespaceNode(this XNode node) + private static bool IsPreTag(this XElement elem) + => elem.Name.LocalName == "pre"; + + private static bool IsBlockTag(this XElement elem) + => BlockTags.Contains(elem.Name.LocalName); +} + +// Define helper methods that are shared between extensions. +static file class HelperMethods +{ + public static bool IsIndentChar(char c) + => c == ' ' || c == '\t'; + + public static bool IsWhitespaceNode(this XNode node) { if (node is not XText textNode) return false; diff --git a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentRemarksTest.cs b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentRemarksTest.cs index 04da4d4184d..f3e785b98fa 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentRemarksTest.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentRemarksTest.cs @@ -30,8 +30,10 @@ public class XmlElement // Expected Markdown """
  • +
    public class XmlElement
                     : XmlLinkedNode
    +
""" ); @@ -64,6 +66,47 @@ public void Remarks_WithCodeBlocks() ); } + // UnitTest for https://github.com/dotnet/docfx/issues/10965 + [Fact] + public void Remarks_WithExample() + { + ValidateRemarks( + // Input XML + """ + + + Message + + + + + + """, + // Expected Markdown + """ + Message + + + +
public class User
+            {
+                aaa
+
+                bbb
+                ccc
+            }
+ +
+ """); + } + private static void ValidateRemarks(string input, string expected) { // Act diff --git a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Code.cs b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Code.cs index 51844bbe682..0b229682e12 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Code.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Code.cs @@ -28,6 +28,28 @@ public void Code_Block() """); } + [Fact] + public void Code_Block_WithoutNewLine() + { + ValidateSummary( + // Input XML + """ + + Paragraph1Paragraph2 + + """, + // Expected Markdown + """ + Paragraph1 + +
DELETE /articles/1 HTTP/1.1
+ + Paragraph2 + """); + } + [Fact] public void Code_Inline() { @@ -47,4 +69,217 @@ public void Code_Inline() Paragraph2 """); } + + + [Fact] + public void Code_HtmlTagExistBefore() + { + ValidateSummary( + // Input XML + """ + + paragraph1 + paragraph2 + + + + """, + // Expected Markdown + """ + paragraph1 + +

paragraph2

+ +
public class Sample
+            {
+                line1
+           
+                line2
+            }
+
public class Sample2
+            {
+                line1
+            
+                line2
+            }
+ """); + } + + [Fact] + public void Code_ParentHtmlTagExist() + { + ValidateSummary( + // Input XML + """ + + Paragraph1 +
+ +
+ Paragraph2 +
+ """, + // Expected Markdown + """ +

Paragraph1

+
+ +
public class Sample
+            {
+                line1
+           
+                line2
+            }
+ +
+

Paragraph2

+ """); + } + + [Fact] + public void Code_MultipleBlockWithoutNewLine() + { + ValidateSummary( + // Input XML + """ + + Paragraph1 + var x = 1;var x = 2; + Paragraph2 + + """, + // Expected Markdown + """ + Paragraph1 + +
var x = 1;
var x = 2;
+ + Paragraph2 + """); + } + + [Fact] + public void Code_StartsWithSameLine() + { + ValidateSummary( + // Input XML + """ + + + Paragraph: + + + """, + // Expected Markdown + """ + + Paragraph: + +
Code
+ +
+ """); + } + + [Fact] + public void Code_SingleLineWithParagraph() + { + ValidateSummary( + // Input XML + """ + + + Paragraph1Code + Paragraph2 + + + """, + // Expected Markdown + """ + + Paragraph1 + +
Code
+ + Paragraph2 +
+ """); + } + + [Fact] + public void Code_SingleLineWithMultipleParagraphs() + { + ValidateSummary( + // Input XML + """ + + + aaa

bbb

cccCodeddd

eee

fff +
+
+ """, + // Expected Markdown + """ + + aaa + +

bbb

+ + ccc + +
Code
+ + ddd + +

eee

+ + fff +
+ """); + } + + [Fact] + public void Code_Indented() + { + ValidateSummary( + // Input XML + """ + + + Paragraph + + Code + + + + """, + // Expected Markdown + """ + + Paragraph + +
Code
+ +
+ """); + } } diff --git a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.List.cs b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.List.cs index e7ec9dd3ae8..61845d42dea 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.List.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.List.cs @@ -114,8 +114,10 @@ public class XmlElement Paragraph1
  • +
    public class XmlElement
                     : XmlLinkedNode
    +
Paragraph2 @@ -168,10 +170,12 @@ loose text not wrapped in description example

This is ref a sample of exception node

  • +
    public class XmlElement
                   : XmlLinkedNode
    +
    1. - word inside list->listItem->list->listItem->para.> + word inside list->listItem->list->listItem->para.> the second line.
    2. item2 in numbered list
  • item2 in bullet list
  • diff --git a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Others.cs b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Others.cs index 58bc835d583..ffa2b5f9fd2 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Others.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentTests/XmlCommentSummaryTest.Others.cs @@ -8,7 +8,7 @@ namespace Docfx.Dotnet.Tests; public partial class XmlCommentSummaryTest { [Fact] - public void Example() + public void ExampleWithParagraph() { ValidateSummary( // Input XML @@ -26,7 +26,9 @@ public void Example() Paragraph1 +
    code content
    +
    Paragraph2 diff --git a/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.Issue10553.cs b/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.Issue10553.cs index 132d0972577..dc572f4499f 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.Issue10553.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.Issue10553.cs @@ -29,8 +29,11 @@ Converts action result without parameters into action result with null parameter var expected = """ Converts action result without parameters into action result with null parameter. -
    return NotFound() -> return NotFound(null)
    +                
    +
    +                
    return NotFound() -> return NotFound(null)
                     return NotFound() -> return NotFound(null)
    +
    This ensures our formatter is invoked, where we'll build a JSON:API compliant response. For details, see: diff --git a/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.cs b/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.cs index e0c3d69cb92..da67ef42974 100644 --- a/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.cs +++ b/test/Docfx.Dotnet.Tests/XmlCommentUnitTest.cs @@ -408,10 +408,12 @@ Classes in assemblies are by definition complete. example

    This is ref a sample of exception node

    • +
      public class XmlElement
                       : XmlLinkedNode
      +
      1. - word inside list->listItem->list->listItem->para.> + word inside list->listItem->list->listItem->para.> the second line.
      2. item2 in numbered list
    • item2 in bullet list
    • @@ -581,6 +583,7 @@ public sealed class Issue10385

      Paragraph.

      +
      public sealed class Issue10385
                   {
                       public int AAA {get;set;}