Repository: jackdewinter/pymarkdown

Issue:
Newer fixes for Block-List-Block with a started fenced code block throws exception.
## Prerequisites

* [X] Are you running the latest version of this application?
* [X] Have you checked the [Frequently Asked Questions](https://pymarkdown.readthedocs.io/en/latest/faq) document?
* [X] Have you simplified the bug report to the essential details?
  * [X] Do you have a distinct command line to report?
  * [X] Can you clearly state the configuration for this bug report?
  * [X] Do you have a minimal document that highlights this bug?
  * [X] Are any required files (configuration or Markdown document) attached to the issue?
* [X] Did you perform a cursory search of other issues to look for related issues?

## Bug Report

### Bug Type

* [X] Assertion Failure

### Description

In cases within a bq-list-bq combination, if a fenced code block is opened within the list and the next line in the list is `> +` (i.e. close the outermost bq and start a new list item on the list), an exception is thrown.

### Specifics

What operating system and version are you running into this behavior on?

Win11

What version are you seeing this behavior in? (Run `pip list` or `pipenv run pip list` and look for the entry beside `pymarkdownlnt`.)

latest

Are there any extra steps that need to be taken before executing the application?

No

What is the command line you invoke to get this behavior?

test_extra_044fc

Are you using a configuration file? Either on the command line or one of the implicit configuration files? If so, attach that file to this issue.

No

What Markdown document causes this behavior to manifest?  Attach that file to this issue.

```
> + > -----
>   > ```block
> + another list
```

### Actual Behavior

An exception is thrown as the parser is trying to shortcut the bq-list-bq processing, and the `+` throws it off.

### Expected Behavior

Should abort the shortcut and resume normal processing for the third line.

Discussion:
sourcery-ai[bot]: <!-- Generated by sourcery-ai[bot]: start review_guide -->

🧙 Sourcery is reviewing your pull request!

---

<details>
<summary>Tips</summary>

- Trigger a new Sourcery review by commenting `@sourcery-ai review` on the pull request.
- Continue your discussion with Sourcery by replying directly to review comments.
- You can change your review settings at any time by accessing your [dashboard](https://app.sourcery.ai):
    - Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    - Change the review language;
- You can always [contact us](mailto:support@sourcery.ai) if you have any questions or feedback.

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/jackdewinter/pymarkdown/pull/1136?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`5d2b0c8`)](https://app.codecov.io/gh/jackdewinter/pymarkdown/commit/5d2b0c8a8cf12fa74d046e4a64f2ed11a0fc27e7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) to head [(`d02165b`)](https://app.codecov.io/gh/jackdewinter/pymarkdown/commit/d02165baa4657225f569c7b3c1a5affca0f744da?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main     #1136   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          190       190           
  Lines        19986     20013   +27     
  Branches      2517      2524    +7     
=========================================
+ Hits         19986     20013   +27     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/jackdewinter/pymarkdown/pull/1136?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/newdocs/src/changelog.md b/newdocs/src/changelog.md
index 511456b7..07f994e3 100644
--- a/newdocs/src/changelog.md
+++ b/newdocs/src/changelog.md
@@ -30,6 +30,11 @@
 - [Issue 1130](https://github.com/jackdewinter/pymarkdown/issues/1130)
     - check for adding extra line to list with blank line in *-List-Bq
       not flexible enough
+- [Issue 1132](https://github.com/jackdewinter/pymarkdown/issues/1132)
+    - false positives (negatives?) for list looseness fixed
+- [Issue 1135](https://github.com/jackdewinter/pymarkdown/issues/1135)
+    - fixed issue introduced with above shortcuting in Bq-List-Bq scenarios
+      to avoid assert
 
 ### Changed
 
diff --git a/publish/coverage.json b/publish/coverage.json
index a69bc695..8ae31d1e 100644
--- a/publish/coverage.json
+++ b/publish/coverage.json
@@ -2,12 +2,12 @@
     "projectName": "pymarkdown",
     "reportSource": "pytest",
     "branchLevel": {
-        "totalMeasured": 5035,
-        "totalCovered": 5035
+        "totalMeasured": 5049,
+        "totalCovered": 5049
     },
     "lineLevel": {
-        "totalMeasured": 19986,
-        "totalCovered": 19986
+        "totalMeasured": 20013,
+        "totalCovered": 20013
     }
 }
 
diff --git a/publish/pylint_suppression.json b/publish/pylint_suppression.json
index 055a5f1f..69b9613a 100644
--- a/publish/pylint_suppression.json
+++ b/publish/pylint_suppression.json
@@ -24,8 +24,8 @@
             "too-many-locals": 3
         },
         "pymarkdown/block_quotes/block_quote_processor.py": {
-            "too-many-arguments": 5,
-            "too-many-locals": 2
+            "too-many-arguments": 6,
+            "too-many-locals": 3
         },
         "pymarkdown/coalesce/coalesce_processor.py": {
             "too-few-public-methods": 1
@@ -502,8 +502,8 @@
         "too-many-instance-attributes": 25,
         "too-many-public-methods": 4,
         "too-few-public-methods": 39,
-        "too-many-arguments": 232,
-        "too-many-locals": 44,
+        "too-many-arguments": 233,
+        "too-many-locals": 45,
         "chained-comparison": 1,
         "too-many-boolean-expressions": 2,
         "protected-access": 25,
diff --git a/pymarkdown/block_quotes/block_quote_processor.py b/pymarkdown/block_quotes/block_quote_processor.py
index 594ff9a5..67be3a67 100644
--- a/pymarkdown/block_quotes/block_quote_processor.py
+++ b/pymarkdown/block_quotes/block_quote_processor.py
@@ -677,10 +677,12 @@ class BlockQuoteProcessor:
             BlockQuoteMarkdownToken, block_stack_token.matching_markdown_token
         )
         assert parser_state.original_line_to_parse is not None
-        character_after_list = parser_state.original_line_to_parse[
+        if character_after_list := parser_state.original_line_to_parse[
             start_index : embedded_list_stack_token.indent_level
-        ].strip()
-        assert not character_after_list
+        ].strip():
+            raise ValueError(
+                f"probably list start character ({character_after_list}) encountered"
+            )
         assert block_quote_data.current_count + 1 == block_quote_data.stack_count
         sd = parser_state.original_line_to_parse[embedded_list_stack_token.indent_level]
         assert sd == ">"
@@ -778,22 +780,76 @@ class BlockQuoteProcessor:
             process_fenced_block
             and block_quote_data.current_count < block_quote_data.stack_count
         ):
-            process_fenced_block, stack_index = (
+            xprocess_fenced_block, xstack_index = (
                 BlockQuoteProcessor.__handle_existing_block_quote_fenced_special(
                     parser_state, start_index, block_quote_data
                 )
             )
-            if not process_fenced_block:
-                return BlockQuoteProcessor.__handle_existing_block_quote_fenced_special_part_two(
-                    parser_state,
-                    stack_index,
-                    line_to_parse,
-                    start_index,
-                    block_quote_data,
-                    leaf_tokens,
-                    container_level_tokens,
-                    avoid_block_starts,
-                )
+            if not xprocess_fenced_block:
+                try:
+                    return BlockQuoteProcessor.__handle_existing_block_quote_fenced_special_part_two(
+                        parser_state,
+                        xstack_index,
+                        line_to_parse,
+                        start_index,
+                        block_quote_data,
+                        leaf_tokens,
+                        container_level_tokens,
+                        avoid_block_starts,
+                    )
+                except ValueError as this_exception:
+                    POGGER.debug(f"escaped special handling due to: {this_exception}")
+
+        return BlockQuoteProcessor.__handlers(
+            process_fenced_block,
+            parser_state,
+            block_quote_data,
+            extracted_whitespace,
+            position_marker,
+            original_start_index,
+            container_start_bq_count,
+            line_to_parse,
+            start_index,
+            leaf_tokens,
+            original_line,
+            last_block_quote_index,
+            avoid_block_starts,
+            container_level_tokens,
+        )
+
+    # pylint: enable=too-many-arguments, too-many-locals
+
+    # pylint: disable=too-many-arguments, too-many-locals
+    @staticmethod
+    def __handlers(
+        process_fenced_block: bool,
+        parser_state: ParserState,
+        block_quote_data: BlockQuoteData,
+        extracted_whitespace: str,
+        position_marker: PositionMarker,
+        original_start_index: int,
+        container_start_bq_count: int,
+        line_to_parse: str,
+        start_index: int,
+        leaf_tokens: List[MarkdownToken],
+        original_line: str,
+        last_block_quote_index: int,
+        avoid_block_starts: bool,
+        container_level_tokens: List[MarkdownToken],
+    ) -> Tuple[
+        str,
+        int,
+        List[MarkdownToken],
+        List[MarkdownToken],
+        BlockQuoteData,
+        int,
+        bool,
+        int,
+        Optional[str],
+        bool,
+        Optional[RequeueLineInfo],
+        bool,
+    ]:
         if not process_fenced_block:
             return BlockQuoteNonFencedHelper.handle_non_fenced_code_section(
                 parser_state,
diff --git a/pymarkdown/transform_gfm/transform_to_gfm_list_looseness.py b/pymarkdown/transform_gfm/transform_to_gfm_list_looseness.py
index 23c254fd..adb087e1 100644
--- a/pymarkdown/transform_gfm/transform_to_gfm_list_looseness.py
+++ b/pymarkdown/transform_gfm/transform_to_gfm_list_looseness.py
@@ -295,6 +295,7 @@ class TransformToGfmListLooseness:
         token_to_check = actual_tokens[check_index]
 
         POGGER.debug("token_to_check-->$", token_to_check)
+        is_loose = False
         if token_to_check.is_blank_line:
             POGGER.debug("before_blank-->$", actual_tokens[check_index - 1])
             if (
@@ -306,8 +307,33 @@ class TransformToGfmListLooseness:
                 POGGER.debug("!!!Starting BQ Blank!!!")
             else:
                 POGGER.debug("!!!LOOSE!!!")
-                return True
-        return False
+                is_loose = True
+        return is_loose and TransformToGfmListLooseness.__is_really_loose(
+            actual_tokens, check_index
+        )
+
+    @staticmethod
+    def __is_really_loose(actual_tokens: List[MarkdownToken], check_index: int) -> bool:
+        real_answer = None
+        inner_containers = 0
+        search_index = check_index - 1
+        while real_answer is None and search_index >= 0:
+            rt = actual_tokens[search_index]
+            if rt.is_block_quote_end or rt.is_list_end:
+                inner_containers += 1
+            elif rt.is_list_start:
+                if inner_containers:
+                    inner_containers -= 1
+                else:
+                    real_answer = True
+            elif rt.is_block_quote_start:
+                if inner_containers:
+                    inner_containers -= 1
+                else:
+                    real_answer = False
+            search_index -= 1
+        assert real_answer is not None, "must always have a real answer"
+        return real_answer
 
     @staticmethod
     def __find_owning_list_start(


Test patch:
diff --git a/publish/test-results.json b/publish/test-results.json
index a3f12871..54f3dfe9 100644
--- a/publish/test-results.json
+++ b/publish/test-results.json
@@ -871,7 +871,7 @@
             "totalTests": 51,
             "failedTests": 0,
             "errorTests": 0,
-            "skippedTests": 1,
+            "skippedTests": 0,
             "elapsedTimeInMilliseconds": 0
         },
         {
@@ -948,10 +948,10 @@
         },
         {
             "name": "test.nested_three.test_markdown_nested_three_unordered_block_ordered",
-            "totalTests": 52,
+            "totalTests": 54,
             "failedTests": 0,
             "errorTests": 0,
-            "skippedTests": 6,
+            "skippedTests": 7,
             "elapsedTimeInMilliseconds": 0
         },
         {
@@ -1623,7 +1623,7 @@
             "totalTests": 180,
             "failedTests": 0,
             "errorTests": 0,
-            "skippedTests": 2,
+            "skippedTests": 1,
             "elapsedTimeInMilliseconds": 0
         },
         {
diff --git a/test/nested_three/test_markdown_nested_three_ordered_block_block.py b/test/nested_three/test_markdown_nested_three_ordered_block_block.py
index a36b5900..bdbcac02 100644
--- a/test/nested_three/test_markdown_nested_three_ordered_block_block.py
+++ b/test/nested_three/test_markdown_nested_three_ordered_block_block.py
@@ -773,7 +773,6 @@ item</p>
 
 
 @pytest.mark.gfm
-@pytest.mark.skip
 def test_nested_three_ordered_nl_block_skip_nl_block_skip_3():
     """
     Verify that a nesting of ordered list, block quote, block quote works
diff --git a/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py b/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py
index 49d2b16d..77587a2e 100644
--- a/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py
+++ b/test/nested_three/test_markdown_nested_three_unordered_block_ordered.py
@@ -1925,10 +1925,11 @@ def test_nested_three_unordered_block_ordered_with_blank_fenced_bb():
         "[text(2,8):Test1:]",
         "[end-para:::True]",
         "[li(3,5):7::2]",
-        "[para(3,8):\n]",
-        "[text(3,8):Test2\nabc::\n]",
-        "[end-para:::True]",
+        "[para(3,8):]",
+        "[text(3,8):Test2:]",
+        "[end-para:::False]",
         "[end-olist:::True]",
+        "[tbreak(4,5):_::___]",
         "[end-block-quote:::True]",
         "[fcode-block(5,3):`:3:text:::::]",
         "[text(6,3):block:]",
@@ -2149,6 +2150,154 @@ abc
     act_and_assert(source_markdown, expected_gfm, expected_tokens)
 
 
+@pytest.mark.gfm
+@pytest.mark.skip
+def test_nested_three_unordered_block_ordered_with_blank_fenced_bf():
+    """
+    TBD - from https://github.com/jackdewinter/pymarkdown/issues/731
+    """
+
+    # Arrange
+    source_markdown = """- Test List
+  > 1) Test1
+  > 2) Test2
+  > # bob
+  > # robert
+  abc
+  # cblock
+  block
+  # uncblock
+"""
+
+    expected_tokens = [
+        "[ulist(1,1):-::2::\n  \n  \n  \n  \n]",
+        "[para(1,3):]",
+        "[text(1,3):Test List:]",
+        "[end-para:::True]",
+        "[block-quote(2,3):  :  > \n  > \n  > \n  > ]",
+        "[olist(2,5):):1:7::]",
+        "[para(2,8):]",
+        "[text(2,8):Test1:]",
+        "[end-para:::True]",
+        "[li(3,5):7::2]",
+        "[para(3,8):]",
+        "[text(3,8):Test2:]",
+        "[end-para:::False]",
+        "[end-olist:::True]",
+        "[atx(4,5):1:0:]",
+        "[text(4,7):bob: ]",
+        "[end-atx::]",
+        "[atx(5,5):1:0:]",
+        "[text(5,7):robert: ]",
+        "[end-atx::]",
+        "[end-block-quote:::False]",
+        "[para(6,3):]",
+        "[text(6,3):abc:]",
+        "[end-para:::False]",
+        "[atx(7,3):1:0:]",
+        "[text(7,5):cblock: ]",
+        "[end-atx::]",
+        "[para(8,3):]",
+        "[text(8,3):block:]",
+        "[end-para:::False]",
+        "[atx(9,3):1:0:]",
+        "[text(9,5):uncblock: ]",
+        "[end-atx::]",
+        "[BLANK(10,1):]",
+        "[end-ulist:::True]",
+    ]
+    expected_gfm = """<ul>
+<li>Test List
+<blockquote>
+<ol>
+<li>Test1</li>
+<li>Test2</li>
+</ol>
+<h1>bob</h1>
+<h1>robert</h1>
+</blockquote>
+abc
+<h1>cblock</h1>
+block
+<h1>uncblock</h1>
+</li>
+</ul>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_nested_three_unordered_block_ordered_with_blank_fenced_bg():
+    """
+    TBD - from https://github.com/jackdewinter/pymarkdown/issues/731
+    """
+
+    # Arrange
+    source_markdown = """- Test List
+  > # Test1
+  > Test2
+  > # bob
+  > robert
+  # abc
+  # cblock
+  block
+  # uncblock
+"""
+
+    expected_tokens = [
+        "[ulist(1,1):-::2::\n\n\n\n  \n  \n  \n  \n]",
+        "[para(1,3):]",
+        "[text(1,3):Test List:]",
+        "[end-para:::True]",
+        "[block-quote(2,3):  :  > \n  > \n  > \n  > ]",
+        "[atx(2,5):1:0:]",
+        "[text(2,7):Test1: ]",
+        "[end-atx::]",
+        "[para(3,5):]",
+        "[text(3,5):Test2:]",
+        "[end-para:::False]",
+        "[atx(4,5):1:0:]",
+        "[text(4,7):bob: ]",
+        "[end-atx::]",
+        "[para(5,5):]",
+        "[text(5,5):robert:]",
+        "[end-para:::True]",
+        "[end-block-quote:::True]",
+        "[atx(6,3):1:0:]",
+        "[text(6,5):abc: ]",
+        "[end-atx::]",
+        "[atx(7,3):1:0:]",
+        "[text(7,5):cblock: ]",
+        "[end-atx::]",
+        "[para(8,3):]",
+        "[text(8,3):block:]",
+        "[end-para:::False]",
+        "[atx(9,3):1:0:]",
+        "[text(9,5):uncblock: ]",
+        "[end-atx::]",
+        "[BLANK(10,1):]",
+        "[end-ulist:::True]",
+    ]
+    expected_gfm = """<ul>
+<li>Test List
+<blockquote>
+<h1>Test1</h1>
+<p>Test2</p>
+<h1>bob</h1>
+<p>robert</p>
+</blockquote>
+<h1>abc</h1>
+<h1>cblock</h1>
+block
+<h1>uncblock</h1>
+</li>
+</ul>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
 @pytest.mark.gfm
 @pytest.mark.skip
 def test_nested_three_unordered_block_ordered_with_blank_fenced_c1():
diff --git a/test/test_markdown_extra.py b/test/test_markdown_extra.py
index 256046f9..5d9ba159 100644
--- a/test/test_markdown_extra.py
+++ b/test/test_markdown_extra.py
@@ -6648,7 +6648,6 @@ def test_extra_044fb():
 
 
 @pytest.mark.gfm
-@pytest.mark.skip
 def test_extra_044fc():
     """
     TBD
@@ -6660,15 +6659,14 @@ def test_extra_044fc():
 > + another list
 """
     expected_tokens = [
-        "[block-quote(1,1)::> ]",
+        "[block-quote(1,1)::> \n> ]",
         "[ulist(1,3):+::4::\n]",
-        "[block-quote(1,5)::> \n>   > \n> ]",
+        "[block-quote(1,5)::> \n>   > ]",
         "[tbreak(1,7):-::-----]",
-        "[para(2,7):]",
-        "[text(2,7):abc:]",
-        "[end-para:::True]",
+        "[fcode-block(2,7):`:3:block:::::]",
+        "[end-fcode-block::::True]",
         "[end-block-quote:::True]",
-        "[li(3,3):4:  :]",
+        "[li(3,3):4::]",
         "[para(3,5):]",
         "[text(3,5):another list:]",
         "[end-para:::True]",
@@ -7639,8 +7637,8 @@ un-block</p>
     act_and_assert(source_markdown, expected_gfm, expected_tokens)
 
 
-@pytest.mark.skip
 @pytest.mark.gfm
+@pytest.mark.skip
 def test_extra_044jec():
     """
     TBD