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 GitHub committed 2026-08-15 10:05:46 +08:00
1 parent cb5a2747da
commit 560146e644
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"; 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').CompileContext} CompileContext
* @typedef {import('mdast-util-from-markdown').Handle} Handle * @typedef {import('mdast-util-from-markdown').Handle} Handle
* @typedef {import('micromark-util-types').State} State * @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
*/ */
const nodeType = "liquidNode"; const nodeType = "liquidNode";
@@ -46,15 +47,37 @@ function liquidFromMarkdown() {
*/ */
function liquidSyntax() { function liquidSyntax() {
return { return {
flow: {
[codes.leftCurlyBrace]: {
name: "liquidFlow",
tokenize: tokenizeFlow,
},
},
text: { text: {
[codes.leftCurlyBrace]: { [codes.leftCurlyBrace]: {
name: "liquid", name: "liquidText",
tokenize, 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} */ /** @type {typeof codes.rightCurlyBrace | typeof codes.percentSign} */
let closingCode; let closingCode;
@@ -74,7 +97,7 @@ function liquidSyntax() {
? codes.percentSign ? codes.percentSign
: codes.rightCurlyBrace; : codes.rightCurlyBrace;
effects.consume(code); effects.consume(code);
return inside; return isFlow && interrupt ? ok : inside;
default: default:
return nok(code); return nok(code);
} }
@@ -95,6 +118,9 @@ function liquidSyntax() {
effects.enter(types.lineEnding); effects.enter(types.lineEnding);
effects.consume(code); effects.consume(code);
effects.exit(types.lineEnding); effects.exit(types.lineEnding);
if (isFlow) {
return afterLineEnding;
}
effects.enter(types.data); effects.enter(types.data);
return inside; 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} */ /** @type {State} */
function mayClose(code) { function mayClose(code) {
if (code === codes.rightCurlyBrace) { if (code === codes.rightCurlyBrace) {
effects.consume(code); effects.consume(code);
effects.exit(types.data); effects.exit(types.data);
effects.exit(nodeType); effects.exit(nodeType);
return ok; return isFlow ? afterClose : ok;
} }
return inside; 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 }) { 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 = const isInlineHTML =
node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type); node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type);
@@ -119,6 +123,9 @@ function shouldPrePrintDoubleHardline(path, options) {
parent.type === "listItem" && parent.type === "listItem" &&
previous.type === "paragraph" && previous.type === "paragraph" &&
previous.position.end.line + 1 === node.position.start.line; 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 !( return !(
isSiblingNode || isSiblingNode ||
@@ -126,7 +133,8 @@ function shouldPrePrintDoubleHardline(path, options) {
isPrevNodePrettierIgnore || isPrevNodePrettierIgnore ||
isBlockHtmlWithoutBlankLineBetweenPrevHtml || isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
isBlockHtmlWithoutBlankLineBetweenPrevParagraph || isBlockHtmlWithoutBlankLineBetweenPrevParagraph ||
isHtmlDirectAfterListItem isHtmlDirectAfterListItem ||
isBlockLiquidWithoutBlankLine
); );
} }
@@ -24,7 +24,8 @@ proseWrap: "always"
{% {%
if user 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> # 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 {{ foo
multiline multiline
@@ -167,7 +169,7 @@ bar baz %}
{{ foo {{ foo
bar baz bar baz
qux }} qux }}
@@ -177,7 +179,7 @@ qux }}
{% foo % {% foo %
bar baz %} bar baz %}
================================================================================ ================================================================================
`; `;
@@ -220,7 +222,7 @@ bar baz %}
{{ foo {{ foo
bar baz bar baz
qux }} qux }}
@@ -230,7 +232,7 @@ qux }}
{% foo % {% 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`] = ` exports[`unbalanced-mismatched-braces.md - {"proseWrap":"always"} format 1`] = `
====================================options===================================== ====================================options=====================================
parsers: ["markdown"] 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