Commit 6dcbad780c

Ian Johnson <ian@ianjohnson.dev>
2024-04-08 11:49:22
Autodoc: fix Markdown indented lists (#19577)
Previously, indentation was not being handled correctly in some cases, causing examples such as `std.json.WriteStream` to be rendered with improper list nesting. Additionally, some more test cases have been added to ensure indentation (or lack of indentation) is handled correctly in some other constructs.
1 parent 355ccee
Changed files (2)
lib
docs
lib/docs/wasm/markdown/Parser.zig
@@ -152,7 +152,7 @@ const Block = struct {
                 ""
             else
                 null,
-            .table => if (unindented.len > 0) unindented else null,
+            .table => if (unindented.len > 0) line else null,
             .table_row => null,
             .heading => null,
             .code_block => code_block: {
@@ -168,7 +168,7 @@ const Block = struct {
                 unindented[1..]
             else
                 null,
-            .paragraph => if (unindented.len > 0) unindented else null,
+            .paragraph => if (unindented.len > 0) line else null,
             .thematic_break => null,
         };
     }
@@ -225,7 +225,7 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
         p.pending_blocks.items.len > 0 and
         p.pending_blocks.getLast().tag == .paragraph)
     {
-        try p.addScratchStringLine(rest_line);
+        try p.addScratchStringLine(mem.trimLeft(u8, rest_line, " \t"));
         return;
     }
 
@@ -271,8 +271,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
             // loose, since we might just be looking at a blank line after the
             // end of the last item in the list. The final determination will be
             // made when appending the next child of the list or list item.
-            const maybe_containing_list = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().tag == .list_item)
-                &p.pending_blocks.items[p.pending_blocks.items.len - 2]
+            const maybe_containing_list_index = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().tag == .list_item)
+                p.pending_blocks.items.len - 2
             else
                 null;
 
@@ -285,8 +285,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
                 try p.addScratchStringLine(rest_line_trimmed);
             }
 
-            if (maybe_containing_list) |containing_list| {
-                containing_list.data.list.last_line_blank = rest_line_trimmed.len == 0;
+            if (maybe_containing_list_index) |containing_list_index| {
+                p.pending_blocks.items[containing_list_index].data.list.last_line_blank = rest_line_trimmed.len == 0;
             }
         },
         .inlines => try p.addScratchStringLine(rest_line_trimmed),
@@ -515,7 +515,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
             .data = .{ .list_item = .{
                 .marker = list_item.marker,
                 .number = list_item.number,
-                .continuation_indent = list_item.continuation_indent,
+                .continuation_indent = indent + list_item.marker_len,
             } },
             .rest = list_item.rest,
         };
@@ -559,7 +559,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
 const ListItemStart = struct {
     marker: Block.Data.ListMarker,
     number: u30,
-    continuation_indent: usize,
+    marker_len: usize,
     rest: []const u8,
 };
 
@@ -568,21 +568,21 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
         return .{
             .marker = .@"-",
             .number = undefined,
-            .continuation_indent = 2,
+            .marker_len = 2,
             .rest = unindented_line[2..],
         };
     } else if (mem.startsWith(u8, unindented_line, "* ")) {
         return .{
             .marker = .@"*",
             .number = undefined,
-            .continuation_indent = 2,
+            .marker_len = 2,
             .rest = unindented_line[2..],
         };
     } else if (mem.startsWith(u8, unindented_line, "+ ")) {
         return .{
             .marker = .@"+",
             .number = undefined,
-            .continuation_indent = 2,
+            .marker_len = 2,
             .rest = unindented_line[2..],
         };
     }
@@ -600,7 +600,7 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
     return .{
         .marker = marker,
         .number = number,
-        .continuation_indent = number_end + 2,
+        .marker_len = number_end + 2,
         .rest = after_number[2..],
     };
 }
lib/docs/wasm/markdown.zig
@@ -376,6 +376,106 @@ test "lists with block content" {
     );
 }
 
+test "indented lists" {
+    try testRender(
+        \\Test:
+        \\ * a1
+        \\ * a2
+        \\      * b1
+        \\      * b2
+        \\
+        \\---
+        \\
+        \\    Test:
+        \\  - One
+        \\Two
+        \\    - Three
+        \\Four
+        \\    Five
+        \\Six
+        \\
+        \\---
+        \\
+        \\None of these items are indented far enough from the previous one to
+        \\start a nested list:
+        \\  - One
+        \\   - Two
+        \\    - Three
+        \\     - Four
+        \\      - Five
+        \\     - Six
+        \\    - Seven
+        \\   - Eight
+        \\  - Nine
+        \\
+        \\---
+        \\
+        \\   - One
+        \\     - Two
+        \\       - Three
+        \\         - Four
+        \\     - Five
+        \\         - Six
+        \\- Seven
+        \\
+    ,
+        \\<p>Test:</p>
+        \\<ul>
+        \\<li>a1</li>
+        \\<li>a2<ul>
+        \\<li>b1</li>
+        \\<li>b2</li>
+        \\</ul>
+        \\</li>
+        \\</ul>
+        \\<hr />
+        \\<p>Test:</p>
+        \\<ul>
+        \\<li>One
+        \\Two<ul>
+        \\<li>Three
+        \\Four
+        \\Five
+        \\Six</li>
+        \\</ul>
+        \\</li>
+        \\</ul>
+        \\<hr />
+        \\<p>None of these items are indented far enough from the previous one to
+        \\start a nested list:</p>
+        \\<ul>
+        \\<li>One</li>
+        \\<li>Two</li>
+        \\<li>Three</li>
+        \\<li>Four</li>
+        \\<li>Five</li>
+        \\<li>Six</li>
+        \\<li>Seven</li>
+        \\<li>Eight</li>
+        \\<li>Nine</li>
+        \\</ul>
+        \\<hr />
+        \\<ul>
+        \\<li>One<ul>
+        \\<li>Two<ul>
+        \\<li>Three<ul>
+        \\<li>Four</li>
+        \\</ul>
+        \\</li>
+        \\</ul>
+        \\</li>
+        \\<li>Five<ul>
+        \\<li>Six</li>
+        \\</ul>
+        \\</li>
+        \\</ul>
+        \\</li>
+        \\<li>Seven</li>
+        \\</ul>
+        \\
+    );
+}
+
 test "tables" {
     try testRender(
         \\| Operator | Meaning          |
@@ -394,6 +494,10 @@ test "tables" {
         \\| :--- | :----: | ----: |
         \\| Left | Center | Right |
         \\
+        \\   | One | Two |
+        \\ | Three |     Four   |
+        \\         | Five | Six |
+        \\
     ,
         \\<table>
         \\<tr>
@@ -446,6 +550,20 @@ test "tables" {
         \\<td style="text-align: right">Right</td>
         \\</tr>
         \\</table>
+        \\<table>
+        \\<tr>
+        \\<td>One</td>
+        \\<td>Two</td>
+        \\</tr>
+        \\<tr>
+        \\<td>Three</td>
+        \\<td>Four</td>
+        \\</tr>
+        \\<tr>
+        \\<td>Five</td>
+        \\<td>Six</td>
+        \\</tr>
+        \\</table>
         \\
     );
 }
@@ -597,6 +715,14 @@ test "code blocks" {
         \\    try std.testing.expect(2 + 2 == 4);
         \\}
         \\```
+        \\   ```
+        \\   Indentation up to the fence is removed.
+        \\        Like this.
+        \\ Doesn't need to be fully indented.
+        \\  ```
+        \\```
+        \\Overly indented closing fence is fine:
+        \\    ```
         \\
     ,
         \\<pre><code>Hello, world!
@@ -608,6 +734,12 @@ test "code blocks" {
         \\    try std.testing.expect(2 + 2 == 4);
         \\}
         \\</code></pre>
+        \\<pre><code>Indentation up to the fence is removed.
+        \\     Like this.
+        \\Doesn't need to be fully indented.
+        \\</code></pre>
+        \\<pre><code>Overly indented closing fence is fine:
+        \\</code></pre>
         \\
     );
 }