Repository: facelessuser/pymdown-extensions

Issue:
SuperFences produces different/"invalid" HTML after 10.11
### Description

SuperFences started producing "invalid" HTML after 10.11.
It happens for code blocks without language that use `hl_lines`, e.g.:
````
``` hl_lines="1"
some
code
```
````

I am aware that it got changed in that version but it breaks the code that is working fine in 10.10.2.

### Minimal Reproduction

1. use the following python code
```python
import markdown
text = ('``` hl_lines="1"\n'
        "test\n"
        "data\n"
        "```\n"
        "\n"
        "some text\n"
        "```sql\n"
        "select * from dual;\n"
        "```")
html = markdown.markdown(text, extensions=['pymdownx.superfences'])
print(html)
```

2. run in 10.10.2:
```html
<pre class="highlight"><code>test
data</code></pre>
<p>some text
<pre class="highlight"><code class="language-sql">select * from dual;</code></pre></p>
```

3. run in 10.11:
```html
<p>``` hl_lines="1"
test
data
<pre class="highlight"><code>
some text
```sql
select * from dual;</code></pre></p>
```

### Version(s) & System Info

- Operating System: Windows 10
- Python Version: 3.12
- Package Version: `pymdown-extensions-10.11`

Discussion:
@facelessuser: This was not an explicitly tested case, so that is most likely why this regression occurred. It is perfectly reasonable for this to be expected to work though.

Most likely, the language is now getting parsed from `hl_lines` in this case, and then we have a weird option of `=` remaining.  The language match should enforce that we have a word boundary and no trailing `=` to be considered a language `\b(?!=)`.

We adjusted the header but also cleaned up the regex to be a bit more precise with the reworked logic. Where before we incidentally allowed an omitted language, now we must explicitly expect it and define it such that it won't be confused with attributes or options: `option="value"`.
@facelessuser: It ends up being slightly more complicated pattern update as we have to account for names with non-word chars, so a simple check of `(?=[\t ]|$)` ends up validating the language specifier much better. So now we don't run into issue when using things like `c++` etc.@facelessuser: The fix has been released. Thanks for the report!

Solution patch:
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index f79007c78..a8d39b656 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 10.11.1
+
+-   **Fix**: SuperFences: Fix regression where an omitted language in conjunction with options in the fenced header
+    can cause a fence to not be parsed.
+
 ## 10.11
 
 -   **NEW**: SuperFences: Allow fenced code to be parsed in the form ` ```lang {.class #id} `.
diff --git a/pymdownx/__meta__.py b/pymdownx/__meta__.py
index fecd67690..1c3b1fb46 100644
--- a/pymdownx/__meta__.py
+++ b/pymdownx/__meta__.py
@@ -185,5 +185,5 @@ def parse_version(ver, pre=False):
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(10, 11, 0, "final")
+__version_info__ = Version(10, 11, 1, "final")
 __version__ = __version_info__._get_canonical()
diff --git a/pymdownx/superfences.py b/pymdownx/superfences.py
index b37f8f580..a534bd5e7 100644
--- a/pymdownx/superfences.py
+++ b/pymdownx/superfences.py
@@ -44,7 +44,7 @@
 RE_NESTED_FENCE_START = re.compile(
     r'''(?x)
     (?P<fence>~{3,}|`{3,})
-    (?:[ \t]*\.?(?P<lang>[\w#.+-]+))?                                           # Language
+    (?:[ \t]*\.?(?P<lang>[\w#.+-]+)(?=[\t ]|$))?                                           # Language
     (?:
         [ \t]*(\{(?P<attrs>[^\n]*)\}) |                                         # Optional attributes or
         (?P<options>


Test patch:
diff --git a/tests/test_extensions/test_superfences.py b/tests/test_extensions/test_superfences.py
index 50dfe11c7..ea4714cae 100644
--- a/tests/test_extensions/test_superfences.py
+++ b/tests/test_extensions/test_superfences.py
@@ -609,6 +609,40 @@ def __init__(self):
             True
         )
 
+    def test_nonword_boundary_language(self):
+        """Test language with non-word boundary."""
+
+        self.check_markdown(
+            """
+            ```c++ hl_lines="1"
+            #include <iostream>
+            ```
+            """,
+            """
+            <div class="highlight"><pre><span></span><code><span class="hll"><span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;iostream&gt;</span>
+            </span></code></pre></div>
+            """,  # noqa: E501
+            True
+        )
+
+    def test_omitted_language(self):
+        """Test when language is omitted."""
+
+        self.check_markdown(
+            """
+            ```hl_lines="1"
+            some
+            code
+            ```
+            """,
+            """
+            <div class="highlight"><pre><span></span><code><span class="hll">some
+            </span>code
+            </code></pre></div>
+            """,
+            True
+        )
+
 
 class TestSuperFencesClassesIds(util.MdCase):
     """Test classes and ids without attribute lists."""