Repository: kivikakk/comrak (Rust)

Issue:
format_commonmark panics on a single list item
Panic occurs when `format_commonmark` input contains a single `NodeValue::Item`:

https://github.com/kivikakk/comrak/blob/v0.29.0/src/cm.rs#L468

It didn't panic before version 0.25.0 (`unwrap` was introduced in #429).


Discussion:
That's an interesting/unexpected use case! I suppose we can revert to the previous behaviour if the stack is indeed unexpectedly empty:

```rust
let list_number = if let Some(last_stack) = self.ol_stack.last_mut() {
    let ln = *last_stack;
    if entering {
        *last_stack += 1;
    }
    ln
} else {
    match node.data.borrow().value {
        NodeValue::Item(ref ni) => ni.start,
        NodeValue::TaskItem(_) => parent.start,
        _ => unreachable!(),
    }
};
```

What do you think? Please feel free to open a PR; it'd also be great to have a test with your use case in mind so it doesn't regress!
That solution works. I'll submit a PR shortly



Patch:
diff --git a/src/cm.rs b/src/cm.rs
index 10cba543..4f362588 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -465,11 +465,19 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
         let marker_width = if parent.list_type == ListType::Bullet {
             2
         } else {
-            let last_stack = self.ol_stack.last_mut().unwrap();
-            let list_number = *last_stack;
-            if entering {
-                *last_stack += 1;
-            }
+            let list_number = if let Some(last_stack) = self.ol_stack.last_mut() {
+                let list_number = *last_stack;
+                if entering {
+                    *last_stack += 1;
+                };
+                list_number
+            } else {
+                match node.data.borrow().value {
+                    NodeValue::Item(ref ni) => ni.start,
+                    NodeValue::TaskItem(_) => parent.start,
+                    _ => unreachable!(),
+                }
+            };
             let list_delim = parent.delimiter;
             write!(
                 listmarker,


Test patch:
diff --git a/src/tests/commonmark.rs b/src/tests/commonmark.rs
index 8add8c12..5d6e2e6c 100644
--- a/src/tests/commonmark.rs
+++ b/src/tests/commonmark.rs
@@ -1,6 +1,6 @@
 use std::cell::RefCell;
 
-use self::nodes::{Ast, LineColumn};
+use self::nodes::{Ast, LineColumn, ListType, NodeList};
 
 use super::*;
 use ntest::test_case;
@@ -41,6 +41,33 @@ fn commonmark_avoids_spurious_backslash() {
     );
 }
 
+#[test]
+fn commonmark_renders_single_list_item() {
+    let arena = Arena::new();
+    let options = Options::default();
+    let empty = LineColumn { line: 0, column: 0 };
+    let ast = |val: NodeValue| arena.alloc(AstNode::new(RefCell::new(Ast::new(val, empty))));
+    let list_options = NodeList {
+        list_type: ListType::Ordered,
+        start: 1,
+        ..Default::default()
+    };
+    let list = ast(NodeValue::List(list_options));
+    let item = ast(NodeValue::Item(list_options));
+    let p = ast(NodeValue::Paragraph);
+    p.append(ast(NodeValue::Text("Item 1".to_owned())));
+    item.append(p);
+    list.append(item);
+    let mut output = vec![];
+    cm::format_document(item, &options, &mut output).unwrap();
+    compare_strs(
+        &String::from_utf8(output).unwrap(),
+        "1. Item 1\n",
+        "rendered",
+        "<synthetic>",
+    );
+}
+
 #[test_case("$$x^2$$ and $1 + 2$ and $`y^2`$", "$$x^2$$ and $1 + 2$ and $`y^2`$\n")]
 #[test_case("$$\nx^2\n$$", "$$\nx^2\n$$\n")]
 #[test_case("```math\nx^2\n```", "``` math\nx^2\n```\n")]