Fix crash with mixed checkbox lists (#449)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9742ee2..95a80c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 6.0.1-dev
+
+* Fix a crash in checkbox lists when mixing checkbox items with
+  non-checkbox items.
+
 ## 6.0.0
 
 * Require Dart 2.17
diff --git a/lib/src/block_syntaxes/list_syntax.dart b/lib/src/block_syntaxes/list_syntax.dart
index 483a0d8..b111744 100644
--- a/lib/src/block_syntaxes/list_syntax.dart
+++ b/lib/src/block_syntaxes/list_syntax.dart
@@ -14,11 +14,11 @@
   final List<String> lines;
 }
 
-/// Invisible string used to placehold for an *unchecked* CheckBox.
+/// Invisible string used to placehold for an *unchecked* Checkbox.
 /// The character is Unicode Zero Width Space (U+200B).
 const indicatorForUncheckedCheckBox = '\u{200B}';
 
-/// Invisible string used to placehold for a *checked* CheckBox.
+/// Invisible string used to placehold for a *checked* Checkbox.
 /// This is 2 Unicode Zero Width Space (U+200B) characters.
 const indicatorForCheckedCheckBox = '\u{200B}\u{200B}';
 
@@ -57,8 +57,11 @@
   Node parse(BlockParser parser) {
     final items = <ListItem>[];
     var childLines = <String>[];
-    final isCheckboxListSubclass =
-        (listTag == 'ol_with_checkbox' || listTag == 'ul_with_checkbox');
+    // TODO(https://github.com/dart-lang/markdown/issues/448) make some fixes to
+    // this area of the code. I think we can follow the same pattern as deciding
+    //whether a list item is a forced block.
+    final isCheckboxList =
+        listTag == 'ol_with_checkbox' || listTag == 'ul_with_checkbox';
 
     void endItem() {
       if (childLines.isNotEmpty) {
@@ -83,7 +86,7 @@
       final leadingSpace =
           _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!;
       final leadingExpandedTabLength = _expandedTabLength(leadingSpace);
-      if (tryMatch(emptyPattern)) {
+      if (emptyPattern.hasMatch(parser.current)) {
         if (emptyPattern.hasMatch(parser.next ?? '')) {
           // Two blank lines ends a list.
           break;
@@ -96,46 +99,38 @@
             .replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength)
             .replaceFirst(indent, '');
         childLines.add(line);
-      } else if (tryMatch(hrPattern)) {
+      } else if (hrPattern.hasMatch(parser.current)) {
         // Horizontal rule takes precedence to a new list item.
         break;
-      } else if ((isCheckboxListSubclass &&
-              (tryMatch(ulWithCheckBoxPattern) ||
-                  tryMatch(olWithCheckBoxPattern))) ||
-          tryMatch(ulPattern) ||
-          tryMatch(olPattern)) {
+      } else if (tryMatch(ulWithPossibleCheckboxPattern) ||
+          tryMatch(olWithPossibleCheckboxPattern)) {
         // We know we have a valid [possibleMatch] now, so capture it.
-        final successfulMatch = possibleMatch!;
-        // The checkbox "subclass" patterns ([ulWithCheckBoxPattern] and
-        // [olWithCheckBoxPattern]) have 2 extra capturing groups at the 5
-        // position to capture the checkbox. These shift the other groups
-        // forward by 2 slots.
-        final cbGroupOffset = isCheckboxListSubclass ? 2 : 0;
-        final precedingWhitespace = successfulMatch[1]!;
-        final digits = successfulMatch[2] ?? '';
+        final match = possibleMatch!;
+        final precedingWhitespace = match[1]!;
+        final digits = match[2] ?? '';
         if (startNumber == null && digits.isNotEmpty) {
           startNumber = int.parse(digits);
         }
-        final marker = successfulMatch[3]!;
+        final marker = match[3]!;
         // [checkBoxIndicator] is always empty unless a checkbox was found.
-        String checkBoxIndicator = '';
-        if (isCheckboxListSubclass) {
-          // Look at checkbox capture group and get checkbox state.
+        String checkboxIndicator = '';
+        final checkbox = match[6];
+        final isCheckboxItem = checkbox != null && isCheckboxList;
+        if (isCheckboxItem) {
           // If we find a checked or unchecked checkbox then we will
           // set [checkBoxIndicator] to one of our invisible
           // codes that we can later detect to know if we need to insert
           // a check or unchecked checkbox when we are inserting the
           // listitem li node.
-          final String checkboxGroup = successfulMatch[5]!.toLowerCase();
-          if (checkboxGroup == '[ ]') {
-            checkBoxIndicator = indicatorForUncheckedCheckBox;
-          } else if (checkboxGroup == '[x]') {
-            checkBoxIndicator = indicatorForCheckedCheckBox;
+          if (checkbox == '[ ]') {
+            checkboxIndicator = indicatorForUncheckedCheckBox;
+          } else if (checkbox == '[x]' || checkbox == '[X]') {
+            checkboxIndicator = indicatorForCheckedCheckBox;
           }
         }
-        final firstWhitespace = successfulMatch[5 + cbGroupOffset] ?? '';
-        final restWhitespace = successfulMatch[6 + cbGroupOffset] ?? '';
-        final content = successfulMatch[7 + cbGroupOffset] ?? '';
+        final firstWhitespace = match[8] ?? '';
+        final restWhitespace = match[9] ?? '';
+        final content = match[10] ?? '';
         final isBlank = content.isEmpty;
         if (listMarker != null && listMarker != marker) {
           // Changing the bullet or ordered list delimiter starts a new list.
@@ -165,7 +160,7 @@
         }
         // End the current list item and start a new one.
         endItem();
-        childLines.add('$checkBoxIndicator$restWhitespace$content');
+        childLines.add('$checkboxIndicator$restWhitespace$content');
       } else if (BlockSyntax.isAtBlockEnd(parser)) {
         // Done with the list.
         break;
@@ -197,7 +192,7 @@
       // for possible invisible checkbox placeholder characters at
       // the start of first node's text to see if we need to insert a checkbox.
       Element? checkboxToInsert;
-      if (isCheckboxListSubclass) {
+      if (isCheckboxList) {
         if (children.isNotEmpty) {
           if (children.first.textContent
               .startsWith(indicatorForCheckedCheckBox)) {
diff --git a/lib/src/patterns.dart b/lib/src/patterns.dart
index f2aa7d8..5602630 100644
--- a/lib/src/patterns.dart
+++ b/lib/src/patterns.dart
@@ -31,32 +31,91 @@
 /// SETEXT should win.
 final hrPattern = RegExp(r'^ {0,3}([-*_])[ \t]*\1[ \t]*\1(?:\1|[ \t])*$');
 
-/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
-/// three leading spaces before the marker and any number of spaces or tabs
-/// after.
+// why `{1}`?
+const _checkbox = r'\[[ xX]{1}\]';
+
+const _groupedWhitespaceAndEverything = r'([ \t])([ \t]*)(.*)';
+
+const _oneToNineDigits = r'\d{1,9}';
+
+const _zeroToFourWhitespace = r'[ \t]{0,4}';
+
+const _zeroToThreeSpaces = '[ ]{0,3}';
+
+/// A line starting with one of these markers: `-`, `*`, `+`.
+///
+/// May have up to three leading spaces before the marker and any number of
+/// spaces or tabs after.
 ///
 /// Contains a dummy group at `[2]`, so that the groups in [ulPattern] and
 /// [olPattern] match up; in both, `[2]` is the length of the number that begins
 /// the list marker.
-final ulPattern = RegExp(r'^([ ]{0,3})()([*+-])(([ \t])([ \t]*)(.*))?$');
+final ulPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    // Empty group for group number alignment with [olPattern].
+    '()'
+    '([*+-])'
+    '($_groupedWhitespaceAndEverything)?\$');
 
-/// Similar to [ulPattern] but with a GitHub style checkbox
-/// `'[ ]'|'[x]'|'[X]'` following the number. The checkbox will
-/// be grabbed by group `[5]` and [ulPattern]'s groups `[5,6,7]` are all
-/// shifted 2 places to be `[7,8,9]`
-final ulWithCheckBoxPattern = RegExp(
-    r'^([ ]{0,3})()([*+-])([ \t]{0,4})(\[[ xX]{1}\])(([ \t])([ \t]*)(.*))?$');
+/// Similar to [ulPattern] but with a GitHub-style checkbox
+/// (`'[ ]'|'[x]'|'[X]'`) following the number.
+///
+/// The checkbox will be grabbed by group `[5]` and [ulPattern]'s groups
+/// `[4]`, `[5]`, and `[6]` are all shifted 2 places to be `[6]`, `[7]`, and
+/// `[8]`.
+final ulWithCheckBoxPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    // Empty group for group number alignment with [olWithCheckBoxPattern].
+    '()'
+    '([*+-])'
+    '($_zeroToFourWhitespace)'
+    '($_checkbox)'
+    '($_groupedWhitespaceAndEverything)?\$');
+
+/// Similar to [ulWithCheckBoxPattern] but the checkbox is optional.
+// TODO(srawlins): This is temporary tech debt. I think we will collapse
+// [ulPattern] and [ulWithCheckBoxPattern] into this one pattern.
+final ulWithPossibleCheckboxPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    // Empty group for group number alignment with [olWithCheckBoxPattern].
+    '()'
+    '([*+-])'
+    '(($_zeroToFourWhitespace)($_checkbox))?'
+    // [7], [8], [9], and [10].
+    '($_groupedWhitespaceAndEverything)?\$');
 
 /// A line starting with a number like `123.`. May have up to three leading
 /// spaces before the marker and any number of spaces or tabs after.
-final olPattern = RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$');
+final olPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    '($_oneToNineDigits)'
+    r'([\.)])'
+    '($_groupedWhitespaceAndEverything)?\$');
 
-/// Similar to [olPattern] but with a GitHub style checkbox
-/// `'[ ]'|'[x]'|'[X]'` following the number. The checkbox will
-/// be grabbed by group `[5]` and [olPattern]'s groups `[5,6,7]` are all
-/// shifted 2 places to be `[7,8,9]`
-final olWithCheckBoxPattern = RegExp(
-    r'^([ ]{0,3})(\d{1,9})([\.)])([ \t]{0,4})(\[[ xX]{1}\])(([ \t])([ \t]*)(.*))?$');
+/// Similar to [olPattern] but with a GitHub-style checkbox
+/// (`'[ ]'|'[x]'|'[X]'`) following the number.
+///
+/// The checkbox will be grabbed by group `[5]` and [olPattern]'s groups
+/// `[4]`, `[5]`, and `[6]` are all shifted 2 places to be `[6]`, `[7]`, and
+/// `[8]`.
+final olWithCheckBoxPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    '($_oneToNineDigits)'
+    r'([\.)])'
+    '($_zeroToFourWhitespace)'
+    '($_checkbox)'
+    '($_groupedWhitespaceAndEverything)?\$');
+
+/// Similar to [olWithCheckBoxPattern] but the checkbox is optional.
+// TODO(srawlins): This is temporary tech debt. I think we will collapse
+// [olPattern] and [olWithCheckBoxPattern] into this one pattern.
+final olWithPossibleCheckboxPattern = RegExp(''
+    '^($_zeroToThreeSpaces)'
+    '($_oneToNineDigits)'
+    r'([\.)])'
+    '(($_zeroToFourWhitespace)($_checkbox))?'
+    // [7], [8], [9], and [10].
+    '($_groupedWhitespaceAndEverything)?\$');
 
 /// A line of hyphens separated by at least one pipe.
 final tablePattern = RegExp(
diff --git a/lib/src/version.dart b/lib/src/version.dart
index 0773426..e3a5fe6 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -1,2 +1,2 @@
 // Generated code. Do not modify.
-const packageVersion = '6.0.0';
+const packageVersion = '6.0.1-dev';
diff --git a/pubspec.yaml b/pubspec.yaml
index 4a69ba5..ef7520c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: markdown
-version: 6.0.0
+version: 6.0.1-dev
 
 description: A portable Markdown library written in Dart that can parse
  Markdown into HTML.
diff --git a/test/extensions/ordered_list_with_checkboxes.unit b/test/extensions/ordered_list_with_checkboxes.unit
new file mode 100644
index 0000000..b45dda5
--- /dev/null
+++ b/test/extensions/ordered_list_with_checkboxes.unit
@@ -0,0 +1,65 @@
+>>> checkbox with space
+1. [ ] one
+2. [ ] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li class="task-list-item"><input type="checkbox"></input>​two</li>
+</ol>
+>>> empty checkbox
+1. [] one
+2. [] two
+<<<
+<ol>
+<li>[] one</li>
+<li>[] two</li>
+</ol>
+>>> checkbox with x
+1. [x] one
+2. [x] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​two</li>
+</ol>
+>>> checkbox with X
+1. [X] one
+2. [X] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​two</li>
+</ol>
+>>> mixed checkboxes
+1. [ ] one
+2. [] two
+3. [x] three
+4. [X] four
+5. five
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li>[] two</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​three</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​four</li>
+<li>five</li>
+</ol>
+>>> mixed leading spaces
+1.[ ] zero
+2. [ ] one
+3.  [ ] two
+4.   [ ] three
+5.    [ ] four
+6.     [ ] five
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​zero</li>
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li class="task-list-item"><input type="checkbox"></input>​two</li>
+<li class="task-list-item"><input type="checkbox"></input>​three</li>
+<li class="task-list-item"><input type="checkbox"></input>​four</li>
+<li>
+<pre><code>[ ] five
+</code></pre>
+</li>
+</ol>
\ No newline at end of file
diff --git a/test/extensions/unordered_list_with_checkboxes.unit b/test/extensions/unordered_list_with_checkboxes.unit
new file mode 100644
index 0000000..2d7fc2b
--- /dev/null
+++ b/test/extensions/unordered_list_with_checkboxes.unit
@@ -0,0 +1,65 @@
+>>> checkbox with space
+* [ ] one
+* [ ] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li class="task-list-item"><input type="checkbox"></input>​two</li>
+</ul>
+>>> empty checkbox
+* [] one
+* [] two
+<<<
+<ul>
+<li>[] one</li>
+<li>[] two</li>
+</ul>
+>>> checkbox with x
+* [x] one
+* [x] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​two</li>
+</ul>
+>>> checkbox with X
+* [X] one
+* [X] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​two</li>
+</ul>
+>>> mixed checkboxes
+* [ ] one
+* [] two
+* [x] three
+* [X] four
+* five
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li>[] two</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​three</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>​​four</li>
+<li>five</li>
+</ul>
+>>> mixed leading spaces
+*[ ] zero
+* [ ] one
+*  [ ] two
+*   [ ] three
+*    [ ] four
+*     [ ] five
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>​zero</li>
+<li class="task-list-item"><input type="checkbox"></input>​one</li>
+<li class="task-list-item"><input type="checkbox"></input>​two</li>
+<li class="task-list-item"><input type="checkbox"></input>​three</li>
+<li class="task-list-item"><input type="checkbox"></input>​four</li>
+<li>
+<pre><code>[ ] five
+</code></pre>
+</li>
+</ul>
\ No newline at end of file
diff --git a/test/markdown_test.dart b/test/markdown_test.dart
index a9f6fde..7bbdd98 100644
--- a/test/markdown_test.dart
+++ b/test/markdown_test.dart
@@ -10,7 +10,11 @@
 void main() async {
   await testDirectory('original');
 
-  // Block syntax extensions
+  // Block syntax extensions.
+  testFile(
+    'extensions/fenced_blockquotes.unit',
+    blockSyntaxes: [const FencedBlockquoteSyntax()],
+  );
   testFile(
     'extensions/fenced_code_blocks.unit',
     blockSyntaxes: [const FencedCodeBlockSyntax()],
@@ -20,6 +24,10 @@
     blockSyntaxes: [const HeaderWithIdSyntax()],
   );
   testFile(
+    'extensions/ordered_list_with_checkboxes.unit',
+    blockSyntaxes: [const OrderedListWithCheckBoxSyntax()],
+  );
+  testFile(
     'extensions/setext_headers_with_ids.unit',
     blockSyntaxes: [const SetextHeaderWithIdSyntax()],
   );
@@ -28,8 +36,8 @@
     blockSyntaxes: [const TableSyntax()],
   );
   testFile(
-    'extensions/fenced_blockquotes.unit',
-    blockSyntaxes: [const FencedBlockquoteSyntax()],
+    'extensions/unordered_list_with_checkboxes.unit',
+    blockSyntaxes: [const UnorderedListWithCheckBoxSyntax()],
   );
 
   // Inline syntax extensions