From 560146e644095ce0a97650263747dbf98bbf8272 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 15 Aug 2026 10:05:46 +0800 Subject: [PATCH] Markdown: Fix liquid flow parsing (#19838) Co-authored-by: codex Co-authored-by: fisker Cheung Co-authored-by: wanxiankai Co-authored-by: seiya <20365512+seiyab@users.noreply.github.com> --- changelog_unreleased/markdown/19730.md | 27 ++++ .../micromark/micromark-extension-liquid.js | 84 ++++++++++++- src/language-markdown/print/children.js | 12 +- .../liquid/__snapshots__/format.test.js.snap | 117 +++++++++++++++++- tests/format/markdown/liquid/issue-19724.md | 20 +++ 5 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 changelog_unreleased/markdown/19730.md create mode 100644 tests/format/markdown/liquid/issue-19724.md diff --git a/changelog_unreleased/markdown/19730.md b/changelog_unreleased/markdown/19730.md new file mode 100644 index 000000000..82aa68636 --- /dev/null +++ b/changelog_unreleased/markdown/19730.md @@ -0,0 +1,27 @@ +#### Preserve Liquid blocks after Markdown tables (#19730 by @wanxiankai, @seiyab) + + +```markdown + +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} + + +| Argument | Type | +| ------------------------- | ---- | +| {% set default_params = { | + + "model": ["str"], + +} %} + + +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +``` diff --git a/src/language-markdown/parse/micromark/micromark-extension-liquid.js b/src/language-markdown/parse/micromark/micromark-extension-liquid.js index cac4a27a4..45b011a28 100644 --- a/src/language-markdown/parse/micromark/micromark-extension-liquid.js +++ b/src/language-markdown/parse/micromark/micromark-extension-liquid.js @@ -1,4 +1,4 @@ -import { markdownLineEnding } from "micromark-util-character"; +import { markdownLineEnding, markdownSpace } from "micromark-util-character"; import { codes, types } from "micromark-util-symbol"; /** @@ -7,6 +7,7 @@ import { codes, types } from "micromark-util-symbol"; * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext * @typedef {import('mdast-util-from-markdown').Handle} Handle * @typedef {import('micromark-util-types').State} State + * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext */ const nodeType = "liquidNode"; @@ -46,15 +47,37 @@ function liquidFromMarkdown() { */ function liquidSyntax() { return { + flow: { + [codes.leftCurlyBrace]: { + name: "liquidFlow", + tokenize: tokenizeFlow, + }, + }, text: { [codes.leftCurlyBrace]: { - name: "liquid", - tokenize, + name: "liquidText", + tokenize: tokenizeText, }, }, }; - function tokenize(effects, ok, nok) { + /** @this {TokenizeContext} */ + function tokenizeFlow(effects, ok, nok) { + return tokenize.call(this, effects, ok, nok, "flow"); + } + + /** @this {TokenizeContext} */ + function tokenizeText(effects, ok, nok) { + return tokenize.call(this, effects, ok, nok, "text"); + } + + /** + * @this {TokenizeContext} + * @param mode {"text" | "flow"} + */ + function tokenize(effects, ok, nok, mode) { + const isFlow = mode === "flow"; + const { interrupt, now, parser } = this; /** @type {typeof codes.rightCurlyBrace | typeof codes.percentSign} */ let closingCode; @@ -74,7 +97,7 @@ function liquidSyntax() { ? codes.percentSign : codes.rightCurlyBrace; effects.consume(code); - return inside; + return isFlow && interrupt ? ok : inside; default: return nok(code); } @@ -95,6 +118,9 @@ function liquidSyntax() { effects.enter(types.lineEnding); effects.consume(code); effects.exit(types.lineEnding); + if (isFlow) { + return afterLineEnding; + } effects.enter(types.data); return inside; } @@ -103,17 +129,63 @@ function liquidSyntax() { } } + /** @type {State} */ + function afterLineEnding(code) { + if (parser.lazy[now().line]) { + return nok(code); + } + + if (markdownLineEnding(code)) { + effects.enter(types.lineEnding); + effects.consume(code); + effects.exit(types.lineEnding); + return afterLineEnding; + } + + effects.enter(types.data); + return inside(code); + } + /** @type {State} */ function mayClose(code) { if (code === codes.rightCurlyBrace) { effects.consume(code); effects.exit(types.data); effects.exit(nodeType); - return ok; + return isFlow ? afterClose : ok; } return inside; } + + /** @type {State} */ + function afterClose(code) { + if (markdownSpace(code)) { + effects.enter(types.whitespace); + effects.consume(code); + return afterWhitespace; + } + + return after(code); + } + + /** @type {State} */ + function afterWhitespace(code) { + if (markdownSpace(code)) { + effects.consume(code); + return afterWhitespace; + } + + effects.exit(types.whitespace); + return after(code); + } + + /** @type {State} */ + function after(code) { + return code === codes.eof || markdownLineEnding(code) + ? ok(code) + : nok(code); + } } } diff --git a/src/language-markdown/print/children.js b/src/language-markdown/print/children.js index be78f2e95..9b313551d 100644 --- a/src/language-markdown/print/children.js +++ b/src/language-markdown/print/children.js @@ -42,7 +42,11 @@ function printChildren(path, options, print, events = {}) { } function shouldPrePrintHardline({ node, parent }) { - const isInlineNode = INLINE_NODE_TYPES.has(node.type); + const isInlineNode = + INLINE_NODE_TYPES.has(node.type) && + !( + node.type === "liquidNode" && !INLINE_NODE_WRAPPER_TYPES.has(parent.type) + ); const isInlineHTML = node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type); @@ -119,6 +123,9 @@ function shouldPrePrintDoubleHardline(path, options) { parent.type === "listItem" && previous.type === "paragraph" && previous.position.end.line + 1 === node.position.start.line; + const isBlockLiquidWithoutBlankLine = + (node.type === "liquidNode" || previous.type === "liquidNode") && + previous.position.end.line + 1 === node.position.start.line; return !( isSiblingNode || @@ -126,7 +133,8 @@ function shouldPrePrintDoubleHardline(path, options) { isPrevNodePrettierIgnore || isBlockHtmlWithoutBlankLineBetweenPrevHtml || isBlockHtmlWithoutBlankLineBetweenPrevParagraph || - isHtmlDirectAfterListItem + isHtmlDirectAfterListItem || + isBlockLiquidWithoutBlankLine ); } diff --git a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap index f7220d61a..75ed735b3 100644 --- a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap +++ b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap @@ -24,7 +24,8 @@ proseWrap: "always" {% if user -%} Hello, {{ user }}! {% endif %} +%} Hello, {{ user }}! +{% endif %} ================================================================================ `; @@ -84,7 +85,8 @@ where does it end }} # Userscripts [{% img github.svg alt:"View on GitHub" title:"View on GitHub" %}](https://github.com/Charcoal-SE/Userscripts) [Build status loading…](//travis-ci.org/Charcoal-SE/userscripts){: .build} -{% css userscripts %} {% js userscripts %} +{% css userscripts %} +{% js userscripts %} {{ foo multiline @@ -167,7 +169,7 @@ bar baz %} {{ foo -bar baz +bar baz qux }} @@ -177,7 +179,7 @@ qux }} {% foo % -bar baz %} +bar baz %} ================================================================================ `; @@ -220,7 +222,7 @@ bar baz %} {{ foo -bar baz +bar baz qux }} @@ -230,7 +232,7 @@ qux }} {% foo % -bar baz %} +bar baz %} ================================================================================ `; @@ -406,6 +408,109 @@ This input uses Hugo shortcode. Prettier doesn't support this explicitly but it ================================================================================ `; +exports[`issue-19724.md - {"proseWrap":"always"} format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +proseWrap: "always" + printWidth: 80 (default) | +=====================================input====================================== +{% macro param_table(params=None) -%} +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +{% endmacro %} + +--- + +> abc +{% abc %} + +--- + +Inline {{ liquid }} stays inline. + +--- + +{% liquid %} followed by text + +=====================================output===================================== +{% macro param_table(params=None) -%} +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +{% endmacro %} + +--- + +> abc +{% abc %} + +--- + +Inline {{ liquid }} stays inline. + +--- + +{% liquid %} followed by text + +================================================================================ +`; + +exports[`issue-19724.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] + printWidth: 80 (default) | +=====================================input====================================== +{% macro param_table(params=None) -%} +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +{% endmacro %} + +--- + +> abc +{% abc %} + +--- + +Inline {{ liquid }} stays inline. + +--- + +{% liquid %} followed by text + +=====================================output===================================== +{% macro param_table(params=None) -%} +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +{% endmacro %} + +--- + +> abc +{% abc %} + +--- + +Inline {{ liquid }} stays inline. + +--- + +{% liquid %} followed by text + +================================================================================ +`; + exports[`unbalanced-mismatched-braces.md - {"proseWrap":"always"} format 1`] = ` ====================================options===================================== parsers: ["markdown"] diff --git a/tests/format/markdown/liquid/issue-19724.md b/tests/format/markdown/liquid/issue-19724.md new file mode 100644 index 000000000..a587b8d8a --- /dev/null +++ b/tests/format/markdown/liquid/issue-19724.md @@ -0,0 +1,20 @@ +{% macro param_table(params=None) -%} +| Argument | Type | +| -------- | ---- | +{% set default_params = { + "model": ["str"], +} %} +{% endmacro %} + +--- + +> abc +{% abc %} + +--- + +Inline {{ liquid }} stays inline. + +--- + +{% liquid %} followed by text