From 107d69c7cb116205731dcf53136b174f587e7b34 Mon Sep 17 00:00:00 2001
From: feiiiiii5 <204683769+feiiiiii5@users.noreply.github.com>
Date: Fri, 2 Oct 2026 11:06:29 +0800
Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20parse=20front=20matte?=
=?UTF-8?q?r=20closed=20by=20the=20YAML=20`...`=20marker?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The `...` document-end marker was tested against the *previous* line,
because `start`/`maximum` are only moved onto the current line after the
test. Two things followed, both silent:
A block whose last line was `...` was dropped entirely, so the front
matter leaked into the document as visible text:
MarkdownIt().use(front_matter_plugin).render("---\ntitle: a\n...\n")
before '
\ntitle: a\n...
\n'
after ''
And where it did parse, the marker stayed in the token content, so
`content` was `'title: a\n...'` rather than `'title: a'`, unlike the
dashed form.
Move the test to after the per-line refresh and mark the line consumed.
`auto_closed` is only read at `state.line = nextLine + (1 if
auto_closed else 0)`, so setting it is what stops the marker being
re-parsed as content.
`test_all` could not catch either half: the front_matter token is
hidden, so only the rendered output is asserted, and a dropped block
renders as visible text rather than failing. The new tests assert the
token itself.
pytest tests -q 514 passed
---
CHANGELOG.md | 16 ++++++++++++++++
mdit_py_plugins/front_matter/index.py | 11 ++++++++---
tests/test_front_matter.py | 24 ++++++++++++++++++++++++
3 files changed, 48 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1b78b3..324134e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Change Log
+## Unreleased
+
+- 🐛 FIX: front matter closed by `...` is parsed correctly
+
+ The YAML document-end marker was tested against the previous line rather than
+ the current one, because `start`/`maximum` were only moved onto the current
+ line after the test. Two things followed: a block whose last line was `...` was
+ dropped entirely, so the front matter leaked into the document as visible text,
+ and where it did parse, the marker itself was included in the token content.
+
+ ```python
+ MarkdownIt().use(front_matter_plugin).render("---\ntitle: a\n...\n")
+ # before: '
\ntitle: a\n...
\n'
+ # after: ''
+ ```
+
## 0.7.0 - 2026-07-19
- ✨ NEW: Add section reference plugin (`section_ref`) (#144)
diff --git a/mdit_py_plugins/front_matter/index.py b/mdit_py_plugins/front_matter/index.py
index 1551d76..3e8ee9b 100644
--- a/mdit_py_plugins/front_matter/index.py
+++ b/mdit_py_plugins/front_matter/index.py
@@ -69,12 +69,17 @@ def _front_matter_rule(
# unclosed block should be autoclosed by end of document.
return False
- if state.src[start:maximum] == "...":
- break
-
start = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
+ if state.src[start:maximum] == "...":
+ # The YAML document-end marker closes the block on this line, so it must
+ # be consumed here: checking it before start/maximum are moved onto the
+ # current line tests the previous one, which both missed a `...` that
+ # ends the document and leaked the marker into the extracted content.
+ auto_closed = True
+ break
+
if start < maximum and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - ```
diff --git a/tests/test_front_matter.py b/tests/test_front_matter.py
index 627cb89..8401e75 100644
--- a/tests/test_front_matter.py
+++ b/tests/test_front_matter.py
@@ -42,6 +42,30 @@ def test_token():
]
+@pytest.mark.parametrize("closer", ["---", "..."])
+def test_token_content_excludes_the_closer(closer):
+ """The closing marker must not be left inside the extracted content.
+
+ ``...`` is the YAML document-end marker and is exercised by the fixture file,
+ so it has to be consumed like the dashed form rather than kept in the block.
+ """
+ md = MarkdownIt("commonmark").use(front_matter_plugin)
+ tokens = md.parse(f"---\na: 1\n{closer}")
+ assert [token.type for token in tokens] == ["front_matter"]
+ assert tokens[0].content == "a: 1"
+
+
+def test_dots_closer_on_the_last_line():
+ """A block closed by ``...`` on the final line is still front matter.
+
+ ``test_all`` cannot see this: the front_matter token is hidden, so only the
+ rendered output is asserted, and a block that is dropped rather than closed
+ renders as visible text instead of disappearing.
+ """
+ md = MarkdownIt("commonmark").use(front_matter_plugin)
+ assert md.render("---\na: 1\n...\n") == md.render("---\na: 1\n---\n") == ""
+
+
def test_short_source():
md = MarkdownIt("commonmark").use(front_matter_plugin)
From 161f0f240e19cd558b9f130c1d3b4e65bd7a4952 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Mon, 9 Nov 2026 20:25:11 +0100
Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20drop=20changelog?=
=?UTF-8?q?=20entry=20(written=20at=20release)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 324134e..a1b78b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,21 +1,5 @@
# Change Log
-## Unreleased
-
-- 🐛 FIX: front matter closed by `...` is parsed correctly
-
- The YAML document-end marker was tested against the previous line rather than
- the current one, because `start`/`maximum` were only moved onto the current
- line after the test. Two things followed: a block whose last line was `...` was
- dropped entirely, so the front matter leaked into the document as visible text,
- and where it did parse, the marker itself was included in the token content.
-
- ```python
- MarkdownIt().use(front_matter_plugin).render("---\ntitle: a\n...\n")
- # before: '
\ntitle: a\n...
\n'
- # after: ''
- ```
-
## 0.7.0 - 2026-07-19
- ✨ NEW: Add section reference plugin (`section_ref`) (#144)
From 1f624d0bc7f42e11b42e0196f586eef8385afc8a Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Mon, 9 Nov 2026 20:26:03 +0100
Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20front=20matter=20`...?=
=?UTF-8?q?`=20closer=20must=20be=20at=20column=200,=20trailing=20spaces?=
=?UTF-8?q?=20allowed?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
YAML's document-end marker is column-0 only, so an indented `...` (for
example inside a block scalar) no longer closes the front matter. Trailing
spaces after it are accepted, as they already are after the `---` closer.
---
mdit_py_plugins/front_matter/index.py | 12 +++++++-----
tests/test_front_matter.py | 2 +-
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/mdit_py_plugins/front_matter/index.py b/mdit_py_plugins/front_matter/index.py
index 3e8ee9b..28a176e 100644
--- a/mdit_py_plugins/front_matter/index.py
+++ b/mdit_py_plugins/front_matter/index.py
@@ -72,11 +72,13 @@ def _front_matter_rule(
start = state.bMarks[nextLine] + state.tShift[nextLine]
maximum = state.eMarks[nextLine]
- if state.src[start:maximum] == "...":
- # The YAML document-end marker closes the block on this line, so it must
- # be consumed here: checking it before start/maximum are moved onto the
- # current line tests the previous one, which both missed a `...` that
- # ends the document and leaked the marker into the extracted content.
+ if (
+ state.sCount[nextLine] == 0
+ and state.src.startswith("...", start)
+ and state.skipSpaces(start + 3) >= maximum
+ ):
+ # YAML document-end marker: `...` at column 0, trailing spaces allowed.
+ # It is checked on the current line and consumed, so it is not content.
auto_closed = True
break
diff --git a/tests/test_front_matter.py b/tests/test_front_matter.py
index 8401e75..00b714c 100644
--- a/tests/test_front_matter.py
+++ b/tests/test_front_matter.py
@@ -42,7 +42,7 @@ def test_token():
]
-@pytest.mark.parametrize("closer", ["---", "..."])
+@pytest.mark.parametrize("closer", ["---", "...", "... "])
def test_token_content_excludes_the_closer(closer):
"""The closing marker must not be left inside the extracted content.
From 96fd4e5ea2283aaa698611e7b5ece19d42459919 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Mon, 9 Nov 2026 20:26:03 +0100
Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=A7=AA=20TEST:=20front=20matter=20fix?=
=?UTF-8?q?tures=20for=20the=20`...`=20closer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
tests/fixtures/front_matter.md | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/tests/fixtures/front_matter.md b/tests/fixtures/front_matter.md
index e01fd4e..d1d5307 100644
--- a/tests/fixtures/front_matter.md
+++ b/tests/fixtures/front_matter.md
@@ -32,6 +32,37 @@ x: 1
Head
.
+should parse triple dots at end of document:
+.
+---
+x: 1
+...
+.
+.
+
+should ignore spaces after triple dots:
+.
+---
+x: 1
+...
+# Head
+.
+
+Head
+.
+
+should not close on indented triple dots:
+.
+---
+text: |
+ ...
+---
+# Head
+.
+
+Head
+.
+
should parse front matter with indentation:
.
---
From f61fa23570c341a03198f1c1374d9f8b90ac8bf7 Mon Sep 17 00:00:00 2001
From: Chris Sewell
Date: Mon, 9 Nov 2026 21:33:01 +0100
Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=A7=AA=20TEST:=20front=20matter=20`..?=
=?UTF-8?q?.`=20must=20be=20alone=20on=20its=20line=20to=20close?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two fixtures pin the tail check on the closer: `....` and `...x` are content,
so the block runs on to the following `---`.
---
tests/fixtures/front_matter.md | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/tests/fixtures/front_matter.md b/tests/fixtures/front_matter.md
index d1d5307..5cff35b 100644
--- a/tests/fixtures/front_matter.md
+++ b/tests/fixtures/front_matter.md
@@ -63,6 +63,30 @@ text: |
Head
.
+should not close on four dots:
+.
+---
+x: 1
+....
+---
+# Head
+.
+
+Head
+.
+
+should not close on triple dots followed by text:
+.
+---
+x: 1
+...x
+---
+# Head
+.
+
+Head
+.
+
should parse front matter with indentation:
.
---