From bf3ca0c8866614ebfe6db30d8b36ec1a7a6af78f Mon Sep 17 00:00:00 2001 From: Seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:04:39 +0900 Subject: [PATCH] Markdown: Fix parse error in liquid plugin (#19411) Co-authored-by: Codex Co-authored-by: fisker Cheung Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../micromark/micromark-extension-liquid.js | 65 ++++++-- .../liquid/__snapshots__/format.test.js.snap | 146 ++++++++++++++++++ tests/format/markdown/liquid/example-2.md | 21 +++ tests/format/markdown/liquid/issue-19386.md | 4 + 4 files changed, 220 insertions(+), 16 deletions(-) create mode 100644 tests/format/markdown/liquid/example-2.md create mode 100644 tests/format/markdown/liquid/issue-19386.md diff --git a/src/language-markdown/parse/micromark/micromark-extension-liquid.js b/src/language-markdown/parse/micromark/micromark-extension-liquid.js index 8d2db09f5..808879086 100644 --- a/src/language-markdown/parse/micromark/micromark-extension-liquid.js +++ b/src/language-markdown/parse/micromark/micromark-extension-liquid.js @@ -17,12 +17,12 @@ const nodeType = "liquidNode"; function liquidFromMarkdown() { return { canContainEols: [nodeType], - enter: { [nodeType]: enterInlineMath }, - exit: { [nodeType]: exitInlineMath }, + enter: { [nodeType]: enter }, + exit: { [nodeType]: exit }, }; /** @type {Handle} */ - function enterInlineMath(token) { + function enter(token) { this.enter( // @ts-expect-error { type: nodeType }, @@ -32,7 +32,7 @@ function liquidFromMarkdown() { } /** @type {Handle} */ - function exitInlineMath(token) { + function exit(token) { const d = this.resume(); /** @type {any} */ const node = this.stack.at(-1); @@ -49,12 +49,12 @@ function liquidSyntax() { text: { [codes.leftCurlyBrace]: { name: "liquid", - tokenize: liquidTokenize, + tokenize, }, }, }; - function liquidTokenize(effects, ok, nok) { + function tokenize(effects, ok, nok) { return start; /** @type {State} */ @@ -69,7 +69,7 @@ function liquidSyntax() { effects.consume(code); return inside; default: - return nok; + return nok(code); } }; } @@ -79,28 +79,61 @@ function liquidSyntax() { switch (code) { case codes.percentSign: case codes.rightCurlyBrace: - effects.consume(code); - return mayExit; + return effects.check({ tokenize: tokenizeClose }, close, data)(code); case codes.eof: - return nok; + return nok(code); default: if (markdownLineEnding(code)) { + effects.exit(types.data); effects.enter(types.lineEnding); effects.consume(code); effects.exit(types.lineEnding); + effects.enter(types.data); return inside; } - effects.consume(code); - return inside; + return data(code); } } /** @type {State} */ - function mayExit(code) { - if (code !== codes.rightCurlyBrace) { - effects.consume(code); - return inside; + function data(code) { + effects.consume(code); + return inside; + } + + function tokenizeClose(effects, ok, nok) { + return startClose; + + /** @type {State} */ + function startClose(code) { + switch (code) { + case codes.percentSign: + case codes.rightCurlyBrace: + effects.consume(code); + return endClose; + default: + return nok(code); + } } + + /** @type {State} */ + function endClose(code) { + if (code !== codes.rightCurlyBrace) { + return nok(code); + } + effects.consume(code); + return ok; + } + } + + /** @type {State} */ + function close(code) { + effects.consume(code); + return closeEnd; + } + + /** @type {State} */ + function closeEnd(code) { effects.consume(code); effects.exit(types.data); effects.exit(nodeType); diff --git a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap index f1319494c..d79547f8f 100644 --- a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap +++ b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap @@ -70,6 +70,152 @@ where does it end }} ================================================================================ `; +exports[`example-2.md - {"proseWrap":"always"} format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +proseWrap: "always" + printWidth: 80 (default) | +=====================================input====================================== +# linebreak after opening brace + +{ + +--- + +# including empty lines + +{{ foo + +bar baz + +qux }} + +--- + +# line break after % + +{% foo % + +bar baz %} + +=====================================output===================================== +# linebreak after opening brace + +{ + +--- + +# including empty lines + +{{ foo + +bar baz + +qux }} + +--- + +# line break after % + +{% foo % + +bar baz %} + +================================================================================ +`; + +exports[`example-2.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] + printWidth: 80 (default) | +=====================================input====================================== +# linebreak after opening brace + +{ + +--- + +# including empty lines + +{{ foo + +bar baz + +qux }} + +--- + +# line break after % + +{% foo % + +bar baz %} + +=====================================output===================================== +# linebreak after opening brace + +{ + +--- + +# including empty lines + +{{ foo + +bar baz + +qux }} + +--- + +# line break after % + +{% foo % + +bar baz %} + +================================================================================ +`; + +exports[`issue-19386.md - {"proseWrap":"always"} format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +proseWrap: "always" + printWidth: 80 (default) | +=====================================input====================================== +{{ rule({ + categories: { + } +}) }} + +=====================================output===================================== +{{ rule({ + categories: { + } +}) }} + +================================================================================ +`; + +exports[`issue-19386.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] + printWidth: 80 (default) | +=====================================input====================================== +{{ rule({ + categories: { + } +}) }} + +=====================================output===================================== +{{ rule({ + categories: { + } +}) }} + +================================================================================ +`; + exports[`unbalanced-mismatched-braces.md - {"proseWrap":"always"} format 1`] = ` ====================================options===================================== parsers: ["markdown"] diff --git a/tests/format/markdown/liquid/example-2.md b/tests/format/markdown/liquid/example-2.md new file mode 100644 index 000000000..55d203bfa --- /dev/null +++ b/tests/format/markdown/liquid/example-2.md @@ -0,0 +1,21 @@ +# linebreak after opening brace + +{ + +--- + +# including empty lines + +{{ foo + +bar baz + +qux }} + +--- + +# line break after % + +{% foo % + +bar baz %} diff --git a/tests/format/markdown/liquid/issue-19386.md b/tests/format/markdown/liquid/issue-19386.md new file mode 100644 index 000000000..7454a8160 --- /dev/null +++ b/tests/format/markdown/liquid/issue-19386.md @@ -0,0 +1,4 @@ +{{ rule({ + categories: { + } +}) }}