Repository: jackdewinter/pymarkdown

Issue:
Python docstrings are mis-treated as code block fences.
Minimal example:

```md
# Minimal test

1. Item
2. Item with code. The multi-line docstring is interpreted as code block fencing.

    ```python
    def foo_fun():
       """ 
       Does nothing. 

       Really.
       """
       pass
    ```

3. Another Item
```

yields:
```sh
$pymarkdownlnt scan test.md
test.md:8:4: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)
test.md:8:8: MD046: Code block style [Expected: fenced; Actual: indented] (code-block-style)
test.md:14:5: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)
test.md:14:5: MD040: Fenced code blocks should have a language specified (fenced-code-language)
test.md:16:4: MD031: Fenced code blocks should be surrounded by blank lines (blanks-around-fences)
```

using `pymarkdownlnt==0.9.5`

Discussion:

Patch:
diff --git a/publish/coverage.json b/publish/coverage.json
index a22ad94b..a0eca8b9 100644
--- a/publish/coverage.json
+++ b/publish/coverage.json
@@ -2,12 +2,12 @@
     "projectName": "pymarkdown",
     "reportSource": "pytest",
     "branchLevel": {
-        "totalMeasured": 3024,
-        "totalCovered": 3024
+        "totalMeasured": 3028,
+        "totalCovered": 3028
     },
     "lineLevel": {
-        "totalMeasured": 10582,
-        "totalCovered": 10582
+        "totalMeasured": 10585,
+        "totalCovered": 10585
     }
 }
 
diff --git a/pymarkdown/container_block_processor.py b/pymarkdown/container_block_processor.py
index fea11531..53919a2e 100644
--- a/pymarkdown/container_block_processor.py
+++ b/pymarkdown/container_block_processor.py
@@ -402,11 +402,11 @@ class ContainerBlockProcessor:
             if parser_state.token_stack[stack_index].is_block_quote:
                 container_used_indent = parser_state.original_line_to_parse.find(">")
                 assert container_used_indent != -1
-                if (
+                assert (
                     parser_state.original_line_to_parse[container_used_indent + 1]
                     == " "
-                ):
-                    container_used_indent += 1
+                )
+                container_used_indent += 1
             else:
                 assert parser_state.token_stack[stack_index].is_list
                 delta = (
@@ -585,9 +585,6 @@ class ContainerBlockProcessor:
             container_depth >= len(parser_state.token_stack) - 1
             and position_marker.index_number == -1
         )
-        need_leading_whitespace_processing = container_depth < (
-            len(parser_state.token_stack) - 1
-        )
         POGGER.debug(
             "need_trailing_indent_processing($) = container_depth($) >= len(token)-1($) and index_number($) == -1",
             need_trailing_indent_processing,
@@ -595,6 +592,15 @@ class ContainerBlockProcessor:
             len(parser_state.token_stack) - 1,
             position_marker.index_number,
         )
+        POGGER.debug("token-stack:$", parser_state.token_stack)
+        need_leading_whitespace_processing = (
+            container_depth < (len(parser_state.token_stack) - 1)
+            and len(extracted_whitespace) >= 4
+            and not (
+                parser_state.token_stack[-1].is_html_block
+                or parser_state.token_stack[-1].is_fenced_code_block
+            )
+        )
         POGGER.debug(
             "need_leading_whitespace_processing($) = "
             + "not is_para_continue($) and container_depth($) < (len(parser_state.token_stack) - 1)($)"


Test patch:
diff --git a/publish/test-results.json b/publish/test-results.json
index 48898a3b..15adc40a 100644
--- a/publish/test-results.json
+++ b/publish/test-results.json
@@ -379,7 +379,15 @@
             "elapsedTimeInMilliseconds": 0
         },
         {
-            "name": "test.gfm.test_markdown_fenced_code_blocks",
+            "name": "test.test_markdown_extra",
+            "totalTests": 78,
+            "failedTests": 0,
+            "errorTests": 0,
+            "skippedTests": 0,
+            "elapsedTimeInMilliseconds": 0
+        },
+        {
+            "name": "test.test_markdown_fenced_code_blocks",
             "totalTests": 68,
             "failedTests": 0,
             "errorTests": 0,
diff --git a/test/test_markdown_extra.py b/test/test_markdown_extra.py
index f93a4140..1dcbbed1 100644
--- a/test/test_markdown_extra.py
+++ b/test/test_markdown_extra.py
@@ -2935,6 +2935,335 @@ that</p>
     act_and_assert(source_markdown, expected_gfm, expected_tokens)
 
 
+@pytest.mark.gfm
+def test_extra_026x():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. item
+2.  ```python
+    def foo_fun():
+       \"\"\" 
+       Does nothing. 
+
+       Really.
+       \"\"\"
+       pass
+    ```
+3. another item
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::    \n    \n    \n\n    \n    \n    \n    \n]",
+        "[para(1,4):]",
+        "[text(1,4):item:]",
+        "[end-para:::True]",
+        "[li(2,1):4::2]",
+        "[fcode-block(2,5):`:3:python:::::]",
+        '[text(3,5):def foo_fun():\n   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a \n   Does nothing. \n\x03\n   Really.\n   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a\n   pass:]',
+        "[end-fcode-block::3:False]",
+        "[li(11,1):3::3]",
+        "[para(11,4):]",
+        "[text(11,4):another item:]",
+        "[end-para:::True]",
+        "[BLANK(12,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>item</li>
+<li>
+<pre><code class="language-python">def foo_fun():
+   &quot;&quot;&quot; 
+   Does nothing. 
+
+   Really.
+   &quot;&quot;&quot;
+   pass
+</code></pre>
+</li>
+<li>another item</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_extra_026a():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """# Minimal
+
+1. Item
+2. Item with code.
+
+    ```python
+    def foo_fun():
+       \"\"\" 
+       Does nothing. 
+       \"\"\"
+       pass
+    ```
+
+3. Another Item"""
+    expected_tokens = [
+        "[atx(1,1):1:0:]",
+        "[text(1,3):Minimal: ]",
+        "[end-atx::]",
+        "[BLANK(2,1):]",
+        "[olist(3,1):.:1:3::\n   \n   \n   \n   \n   \n   \n   \n]",
+        "[para(3,4):]",
+        "[text(3,4):Item:]",
+        "[end-para:::True]",
+        "[li(4,1):3::2]",
+        "[para(4,4):]",
+        "[text(4,4):Item with code.:]",
+        "[end-para:::True]",
+        "[BLANK(5,1):]",
+        "[fcode-block(6,5):`:3:python:::: :]",
+        '[text(7,4):def foo_fun():\n\a \a\x03\a   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a \n\a \a\x03\a   Does nothing. \n\a \a\x03\a   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a\n\a \a\x03\a   pass:\a \a\x03\a]',
+        "[end-fcode-block: :3:False]",
+        "[BLANK(13,1):]",
+        "[li(14,1):3::3]",
+        "[para(14,4):]",
+        "[text(14,4):Another Item:]",
+        "[end-para:::True]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<h1>Minimal</h1>
+<ol>
+<li>
+<p>Item</p>
+</li>
+<li>
+<p>Item with code.</p>
+<pre><code class="language-python">def foo_fun():
+   &quot;&quot;&quot; 
+   Does nothing. 
+   &quot;&quot;&quot;
+   pass
+</code></pre>
+</li>
+<li>
+<p>Another Item</p>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens, show_debug=False)
+
+
+@pytest.mark.gfm
+def test_extra_026b():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """2. Item with code.
+
+    ```python
+    def foo_fun():
+       \"\"\" 
+       Does nothing. 
+       \"\"\"
+       pass
+    ```
+"""
+    expected_tokens = [
+        "[olist(1,1):.:2:3::\n   \n   \n   \n   \n   \n   \n   \n]",
+        "[para(1,4):]",
+        "[text(1,4):Item with code.:]",
+        "[end-para:::True]",
+        "[BLANK(2,1):]",
+        "[fcode-block(3,5):`:3:python:::: :]",
+        '[text(4,4):def foo_fun():\n\a \a\x03\a   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a \n\a \a\x03\a   Does nothing. \n\a \a\x03\a   \a"\a&quot;\a\a"\a&quot;\a\a"\a&quot;\a\n\a \a\x03\a   pass:\a \a\x03\a]',
+        "[end-fcode-block: :3:False]",
+        "[BLANK(10,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol start="2">
+<li>
+<p>Item with code.</p>
+<pre><code class="language-python">def foo_fun():
+   &quot;&quot;&quot; 
+   Does nothing. 
+   &quot;&quot;&quot;
+   pass
+</code></pre>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens, show_debug=False)
+
+
+@pytest.mark.gfm
+def test_extra_026cx():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. Item with code.
+      ## this
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::   \n]",
+        "[para(1,4):]",
+        "[text(1,4):Item with code.:]",
+        "[end-para:::False]",
+        "[atx(2,7):2:0:   ]",
+        "[text(2,10):this: ]",
+        "[end-atx::]",
+        "[BLANK(3,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>Item with code.
+<h2>this</h2>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_extra_026ca():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. Item with code.
+       ## this
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::   \n]",
+        "[para(1,4):\n    ]",
+        "[text(1,4):Item with code.\n## this::\n]",
+        "[end-para:::True]",
+        "[BLANK(3,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>Item with code.
+## this</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_extra_026cb():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. Item with code.
+   <html>
+      <title>fred</title>
+   </html>
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::   \n   \n   \n]",
+        "[para(1,4):]",
+        "[text(1,4):Item with code.:]",
+        "[end-para:::False]",
+        "[html-block(2,4)]",
+        "[text(2,4):<html>\n   <title>fred</title>\n</html>:]",
+        "[end-html-block:::False]",
+        "[BLANK(5,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>Item with code.
+<html>
+   <title>fred</title>
+</html>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_extra_026cc():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. Item with code.
+   <html>
+       <title>fred</title>
+   </html>
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::   \n   \n   \n]",
+        "[para(1,4):]",
+        "[text(1,4):Item with code.:]",
+        "[end-para:::False]",
+        "[html-block(2,4)]",
+        "[text(2,4):<html>\n    <title>fred</title>\n</html>:]",
+        "[end-html-block:::False]",
+        "[BLANK(5,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>Item with code.
+<html>
+    <title>fred</title>
+</html>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
+@pytest.mark.gfm
+def test_extra_026cd():
+    """
+    TBD
+    """
+
+    # Arrange
+    source_markdown = """1. Item with code.
+   ```text
+       this is some text
+   ```
+"""
+    expected_tokens = [
+        "[olist(1,1):.:1:3::   \n   \n   \n]",
+        "[para(1,4):]",
+        "[text(1,4):Item with code.:]",
+        "[end-para:::False]",
+        "[fcode-block(2,4):`:3:text:::::]",
+        "[text(3,4):this is some text:    ]",
+        "[end-fcode-block::3:False]",
+        "[BLANK(5,1):]",
+        "[end-olist:::True]",
+    ]
+    expected_gfm = """<ol>
+<li>Item with code.
+<pre><code class="language-text">    this is some text
+</code></pre>
+</li>
+</ol>"""
+
+    # Act & Assert
+    act_and_assert(source_markdown, expected_gfm, expected_tokens)
+
+
 @pytest.mark.gfm
 def test_extra_999():
     """