Markdown: Fix liquid flow parsing (#19838)

Co-authored-by: codex <codex@openai.com>
Co-authored-by: fisker Cheung <lionkay@gmail.com>
Co-authored-by: wanxiankai <xiankai.wan@gmail.com>
Co-authored-by: seiya <20365512+seiyab@users.noreply.github.com>
This commit is contained in:
authored and fisker committed 2026-09-16 15:36:51 +08:00
1 parent e546c510dd
commit b766f660b1
5 files changed
+246 -14

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
#### Preserve Liquid blocks after Markdown tables (#19730 by @wanxiankai, @seiyab)
<!-- prettier-ignore -->
```markdown
<!-- Input -->
| Argument | Type |
| -------- | ---- |
{% set default_params = {
"model": ["str"],
} %}
<!-- Prettier stable -->
| Argument | Type |
| ------------------------- | ---- |
| {% set default_params = { |
"model": ["str"],
} %}
<!-- Prettier main -->
| Argument | Type |
| -------- | ---- |
{% set default_params = {
"model": ["str"],
} %}
```
@@ -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);
}
}
}
+10 -2
View File
@@ -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
);
}
@@ -24,7 +24,8 @@ proseWrap: "always"
{%
if user
%} Hello, {{ user }}! {% endif %}
%} Hello, {{ user }}!
{% endif %}
================================================================================
`;
@@ -84,7 +85,8 @@ where does it end }}
# Userscripts <span>[{% img github.svg alt:"View on GitHub" title:"View on GitHub" %}](https://github.com/Charcoal-SE/Userscripts) [Build <span>status loading…</span>](//travis-ci.org/Charcoal-SE/userscripts){: .build}</span>
{% 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"]
@@ -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