authorgravatar for ian@ianjohnson.devIan Johnson <ian@ianjohnson.dev> 2024-04-08 05:49:22-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-04-08 09:49:22+00:00
log6dcbad780cb716fe1d2a4b2ce201a757ea7f03a4
tree409a785cc7e9c79b96c701ac593be3b92a571c5f
parent355cceebc7104d2e818b4959b0e43f822e19a9b5
signaturebadge-check Signed by PGP key B5690EEEBB952194

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.

2 files changed, 145 insertions(+), 13 deletions(-)

lib/docs/wasm/markdown.zig+132
...@@ -376,6 +376,106 @@ test "lists with block content" {...@@ -376,6 +376,106 @@ test "lists with block content" {
376 );376 );
377}377}
378378
379test "indented lists" {
380 try testRender(
381 \\Test:
382 \\ * a1
383 \\ * a2
384 \\ * b1
385 \\ * b2
386 \\
387 \\---
388 \\
389 \\ Test:
390 \\ - One
391 \\Two
392 \\ - Three
393 \\Four
394 \\ Five
395 \\Six
396 \\
397 \\---
398 \\
399 \\None of these items are indented far enough from the previous one to
400 \\start a nested list:
401 \\ - One
402 \\ - Two
403 \\ - Three
404 \\ - Four
405 \\ - Five
406 \\ - Six
407 \\ - Seven
408 \\ - Eight
409 \\ - Nine
410 \\
411 \\---
412 \\
413 \\ - One
414 \\ - Two
415 \\ - Three
416 \\ - Four
417 \\ - Five
418 \\ - Six
419 \\- Seven
420 \\
421 ,
422 \\<p>Test:</p>
423 \\<ul>
424 \\<li>a1</li>
425 \\<li>a2<ul>
426 \\<li>b1</li>
427 \\<li>b2</li>
428 \\</ul>
429 \\</li>
430 \\</ul>
431 \\<hr />
432 \\<p>Test:</p>
433 \\<ul>
434 \\<li>One
435 \\Two<ul>
436 \\<li>Three
437 \\Four
438 \\Five
439 \\Six</li>
440 \\</ul>
441 \\</li>
442 \\</ul>
443 \\<hr />
444 \\<p>None of these items are indented far enough from the previous one to
445 \\start a nested list:</p>
446 \\<ul>
447 \\<li>One</li>
448 \\<li>Two</li>
449 \\<li>Three</li>
450 \\<li>Four</li>
451 \\<li>Five</li>
452 \\<li>Six</li>
453 \\<li>Seven</li>
454 \\<li>Eight</li>
455 \\<li>Nine</li>
456 \\</ul>
457 \\<hr />
458 \\<ul>
459 \\<li>One<ul>
460 \\<li>Two<ul>
461 \\<li>Three<ul>
462 \\<li>Four</li>
463 \\</ul>
464 \\</li>
465 \\</ul>
466 \\</li>
467 \\<li>Five<ul>
468 \\<li>Six</li>
469 \\</ul>
470 \\</li>
471 \\</ul>
472 \\</li>
473 \\<li>Seven</li>
474 \\</ul>
475 \\
476 );
477}
478
379test "tables" {479test "tables" {
380 try testRender(480 try testRender(
381 \\| Operator | Meaning |481 \\| Operator | Meaning |
...@@ -394,6 +494,10 @@ test "tables" {...@@ -394,6 +494,10 @@ test "tables" {
394 \\| :--- | :----: | ----: |494 \\| :--- | :----: | ----: |
395 \\| Left | Center | Right |495 \\| Left | Center | Right |
396 \\496 \\
497 \\ | One | Two |
498 \\ | Three | Four |
499 \\ | Five | Six |
500 \\
397 ,501 ,
398 \\<table>502 \\<table>
399 \\<tr>503 \\<tr>
...@@ -446,6 +550,20 @@ test "tables" {...@@ -446,6 +550,20 @@ test "tables" {
446 \\<td style="text-align: right">Right</td>550 \\<td style="text-align: right">Right</td>
447 \\</tr>551 \\</tr>
448 \\</table>552 \\</table>
553 \\<table>
554 \\<tr>
555 \\<td>One</td>
556 \\<td>Two</td>
557 \\</tr>
558 \\<tr>
559 \\<td>Three</td>
560 \\<td>Four</td>
561 \\</tr>
562 \\<tr>
563 \\<td>Five</td>
564 \\<td>Six</td>
565 \\</tr>
566 \\</table>
449 \\567 \\
450 );568 );
451}569}
...@@ -597,6 +715,14 @@ test "code blocks" {...@@ -597,6 +715,14 @@ test "code blocks" {
597 \\ try std.testing.expect(2 + 2 == 4);715 \\ try std.testing.expect(2 + 2 == 4);
598 \\}716 \\}
599 \\```717 \\```
718 \\ ```
719 \\ Indentation up to the fence is removed.
720 \\ Like this.
721 \\ Doesn't need to be fully indented.
722 \\ ```
723 \\```
724 \\Overly indented closing fence is fine:
725 \\ ```
600 \\726 \\
601 ,727 ,
602 \\<pre><code>Hello, world!728 \\<pre><code>Hello, world!
...@@ -608,6 +734,12 @@ test "code blocks" {...@@ -608,6 +734,12 @@ test "code blocks" {
608 \\ try std.testing.expect(2 + 2 == 4);734 \\ try std.testing.expect(2 + 2 == 4);
609 \\}735 \\}
610 \\</code></pre>736 \\</code></pre>
737 \\<pre><code>Indentation up to the fence is removed.
738 \\ Like this.
739 \\Doesn't need to be fully indented.
740 \\</code></pre>
741 \\<pre><code>Overly indented closing fence is fine:
742 \\</code></pre>
611 \\743 \\
612 );744 );
613}745}
lib/docs/wasm/markdown/Parser.zig+13-13
...@@ -152,7 +152,7 @@ const Block = struct {...@@ -152,7 +152,7 @@ const Block = struct {
152 ""152 ""
153 else153 else
154 null,154 null,
155 .table => if (unindented.len > 0) unindented else null,155 .table => if (unindented.len > 0) line else null,
156 .table_row => null,156 .table_row => null,
157 .heading => null,157 .heading => null,
158 .code_block => code_block: {158 .code_block => code_block: {
...@@ -168,7 +168,7 @@ const Block = struct {...@@ -168,7 +168,7 @@ const Block = struct {
168 unindented[1..]168 unindented[1..]
169 else169 else
170 null,170 null,
171 .paragraph => if (unindented.len > 0) unindented else null,171 .paragraph => if (unindented.len > 0) line else null,
172 .thematic_break => null,172 .thematic_break => null,
173 };173 };
174 }174 }
...@@ -225,7 +225,7 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {...@@ -225,7 +225,7 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
225 p.pending_blocks.items.len > 0 and225 p.pending_blocks.items.len > 0 and
226 p.pending_blocks.getLast().tag == .paragraph)226 p.pending_blocks.getLast().tag == .paragraph)
227 {227 {
228 try p.addScratchStringLine(rest_line);228 try p.addScratchStringLine(mem.trimLeft(u8, rest_line, " \t"));
229 return;229 return;
230 }230 }
231231
...@@ -271,8 +271,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {...@@ -271,8 +271,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
271 // loose, since we might just be looking at a blank line after the271 // loose, since we might just be looking at a blank line after the
272 // end of the last item in the list. The final determination will be272 // end of the last item in the list. The final determination will be
273 // made when appending the next child of the list or list item.273 // made when appending the next child of the list or list item.
274 const maybe_containing_list = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().tag == .list_item)274 const maybe_containing_list_index = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().tag == .list_item)
275 &p.pending_blocks.items[p.pending_blocks.items.len - 2]275 p.pending_blocks.items.len - 2
276 else276 else
277 null;277 null;
278278
...@@ -285,8 +285,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {...@@ -285,8 +285,8 @@ pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
285 try p.addScratchStringLine(rest_line_trimmed);285 try p.addScratchStringLine(rest_line_trimmed);
286 }286 }
287287
288 if (maybe_containing_list) |containing_list| {288 if (maybe_containing_list_index) |containing_list_index| {
289 containing_list.data.list.last_line_blank = rest_line_trimmed.len == 0;289 p.pending_blocks.items[containing_list_index].data.list.last_line_blank = rest_line_trimmed.len == 0;
290 }290 }
291 },291 },
292 .inlines => try p.addScratchStringLine(rest_line_trimmed),292 .inlines => try p.addScratchStringLine(rest_line_trimmed),
...@@ -515,7 +515,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {...@@ -515,7 +515,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
515 .data = .{ .list_item = .{515 .data = .{ .list_item = .{
516 .marker = list_item.marker,516 .marker = list_item.marker,
517 .number = list_item.number,517 .number = list_item.number,
518 .continuation_indent = list_item.continuation_indent,518 .continuation_indent = indent + list_item.marker_len,
519 } },519 } },
520 .rest = list_item.rest,520 .rest = list_item.rest,
521 };521 };
...@@ -559,7 +559,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {...@@ -559,7 +559,7 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
559const ListItemStart = struct {559const ListItemStart = struct {
560 marker: Block.Data.ListMarker,560 marker: Block.Data.ListMarker,
561 number: u30,561 number: u30,
562 continuation_indent: usize,562 marker_len: usize,
563 rest: []const u8,563 rest: []const u8,
564};564};
565565
...@@ -568,21 +568,21 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {...@@ -568,21 +568,21 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
568 return .{568 return .{
569 .marker = .@"-",569 .marker = .@"-",
570 .number = undefined,570 .number = undefined,
571 .continuation_indent = 2,571 .marker_len = 2,
572 .rest = unindented_line[2..],572 .rest = unindented_line[2..],
573 };573 };
574 } else if (mem.startsWith(u8, unindented_line, "* ")) {574 } else if (mem.startsWith(u8, unindented_line, "* ")) {
575 return .{575 return .{
576 .marker = .@"*",576 .marker = .@"*",
577 .number = undefined,577 .number = undefined,
578 .continuation_indent = 2,578 .marker_len = 2,
579 .rest = unindented_line[2..],579 .rest = unindented_line[2..],
580 };580 };
581 } else if (mem.startsWith(u8, unindented_line, "+ ")) {581 } else if (mem.startsWith(u8, unindented_line, "+ ")) {
582 return .{582 return .{
583 .marker = .@"+",583 .marker = .@"+",
584 .number = undefined,584 .number = undefined,
585 .continuation_indent = 2,585 .marker_len = 2,
586 .rest = unindented_line[2..],586 .rest = unindented_line[2..],
587 };587 };
588 }588 }
...@@ -600,7 +600,7 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {...@@ -600,7 +600,7 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
600 return .{600 return .{
601 .marker = marker,601 .marker = marker,
602 .number = number,602 .number = number,
603 .continuation_indent = number_end + 2,603 .marker_len = number_end + 2,
604 .rest = after_number[2..],604 .rest = after_number[2..],
605 };605 };
606}606}