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 '
\n

title: 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: '
\n

title: 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: '
\n

title: 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: . ---