fix(markdown): preserve newline around Chinese/Japanese characters not around Korean letters in all proseWrap settings (#20143)
Internal ESLint Plugin Test / Test (push) Canceled after 0s
Lint / Lint (push) Canceled after 0s
Build / Build (push) Canceled after 0s
Prod / Lint (push) Canceled after 0s
Prod / Node.js 14 on MacOS (push) Canceled after 0s
Prod / Node.js 14 on Linux (push) Canceled after 0s
Prod / Node.js 14 on Windows (push) Canceled after 0s
Prod / Node.js 16 on Linux (push) Canceled after 0s
Prod / Node.js 18 on Linux (push) Canceled after 0s
Prod / Node.js 20 on Linux (push) Canceled after 0s
Prod / Node.js 22 on Linux (push) Canceled after 0s
Prod / Node.js 24 on Linux (push) Canceled after 0s
Prod / Node.js 26 on MacOS (push) Canceled after 0s
Prod / Node.js 26 on Windows (push) Canceled after 0s
Prod / [Full Test] Node.js 26 on Linux (push) Canceled after 0s
Prod / Browser(firefox) on Linux (push) Canceled after 0s
Prod / Browser(firefox) on Windows (push) Canceled after 0s
Prod / [Full Test] Browser(chrome) on Linux (push) Canceled after 0s
Prod / CLI (push) Canceled after 0s
Prod / Preview release (push) Canceled after 0s
Release Script Test / Test (push) Canceled after 0s
Dev / Node.js 22 on MacOS (push) Canceled after 0s
Dev / Node.js 22 on Linux (push) Canceled after 0s
Dev / Node.js 22 on Windows (push) Canceled after 0s
Dev / Node.js 26 on MacOS (push) Canceled after 0s
Dev / Node.js 26 on Windows (push) Canceled after 0s
Dev / [Full Test] Node.js 26 on Linux (push) Canceled after 0s
Prod / Build (push) Canceled after 0s

Co-authored-by: fisker Cheung <lionkay@gmail.com>
This commit is contained in:
Tatsunori Uchinoandfisker Cheung authored and GitHub committed 2026-10-04 11:20:28 +08:00
1 parent 43432349d4
commit 5927216227
7 files changed
+181 -13

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
#### Preserve newlines around Chinese/Japanese characters not around Korean letters in all proseWrap settings (#20143 by @tats-u)
<!-- prettier-ignore -->
```md
<!-- Input -->
English
English
日本語
日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語
日本語
한국어
한국어
中文
中文!
한국어
(中文)
<!-- Prettier stable (--prose-wrap=never) -->
English English日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語 한국어 한국어 中文中文!한국어(中文)
<!-- Prettier main -->
English English
日本語
日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語
日本語 한국어 한국어 中文
中文!
한국어
(中文)
```
+34 -1
View File
@@ -251,7 +251,10 @@ function isBreakable(path, value, proseWrap, isLink, options) {
* normalized form of link labels. https://spec.commonmark.org/0.30/#matches * normalized form of link labels. https://spec.commonmark.org/0.30/#matches
*/ */
function printWhitespace(path, value, proseWrap, isLink, options) { function printWhitespace(path, value, proseWrap, isLink, options) {
if (proseWrap === "preserve" && value === "\n") { if (
(proseWrap === "preserve" || isLineBreakAmbiguous(path)) &&
value === "\n"
) {
return hardline; return hardline;
} }
@@ -276,6 +279,36 @@ function printWhitespaceNode(path, options) {
); );
} }
/**
* Determines if a line break around a CJ character can be handled differently by browsers.
*
* If `true`, since Prettier is just a formatter, which should not change the semantics of the code, it should leave such an ambiguous line break as-is
* and let the browser decide how to interpret it regardless of `proseWrap`.
*
* Once all browsers behave consistently in the future, this function will no longer be needed.
*
* @param {AstPath} path
* @returns {boolean} `true` if a line break around a CJ character are removed by some browsers but converted to a space by others.
*/
function isLineBreakAmbiguous(path) {
const { previous, next } = path;
// All browsers treat a line break between a CJ letter and a non-CJ character as
// equivalent to a space. However, Prettier v3 intentionally decided that, in
// some cases, such a line break is equivalent to removing it without
// converting it to a space, so in v3 we leave it as-is instead of converting it
// to a space or removing it.
// See also: https://github.com/prettier/prettier/pull/16805
return (
(Boolean(previous?.isCJ) || Boolean(next?.isCJ)) &&
// A line break between a CJ letter and a Korean letter has already been interchangeable with a space since Prettier v3.0.0.
// A line break between a CJK punctuation character and a Korean letter is handled differently depending on the browser and the content language.
!(
(previous.kind === KIND_K_LETTER && next.kind === KIND_CJ_LETTER) ||
(next.kind === KIND_K_LETTER && previous.kind === KIND_CJ_LETTER)
)
);
}
function shouldPreventBreak(path, options) { function shouldPreventBreak(path, options) {
const { proseWrap } = options; const { proseWrap } = options;
if (proseWrap === "never") { if (proseWrap === "never") {
@@ -43,7 +43,9 @@ This ia an english paragraph with a CJK quote “中文“.
扩展运算符(spread)是三个点(\`...\`)。 扩展运算符(spread)是三个点(\`...\`)。
::: warning 注意该网站在国外无法访问,故以下演示无效 ::: ::: warning 注意
该网站在国外无法访问,故以下演示无效
:::
IVS 麻󠄁羽󠄀‼️ IVS 麻󠄁羽󠄀‼️
@@ -97,7 +99,9 @@ This ia an english paragraph with a CJK quote “中文“.
扩展运算符(spread)是三个点(\`...\`)。 扩展运算符(spread)是三个点(\`...\`)。
::: warning 注意该网站在国外无法访问,故以下演示无效 ::: ::: warning 注意
该网站在国外无法访问,故以下演示无效
:::
IVS 麻󠄁羽󠄀‼️ IVS 麻󠄁羽󠄀‼️
@@ -38,6 +38,36 @@ nasalカ゚キ゚ク゚ケ゚コ゚カ゚キ゚ク゚ケ゚コ゚カ゚キ゚ク
================================================================================ ================================================================================
`; `;
exports[`cjkMixed.md - {"proseWrap":"always"} format 1`] = `
====================================options=====================================
parsers: ["markdown"]
proseWrap: "always"
printWidth: 80 (default) |
=====================================input======================================
English
English
日本語
日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語
日本語
한국어
한국어
中文
中文!
한국어
(中文)
=====================================output=====================================
English English
日本語
日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語
日本語 한국어 한국어 中文
中文!
한국어
(中文)
================================================================================
`;
exports[`han-kana-alnum.md - {"proseWrap":"always"} format 1`] = ` exports[`han-kana-alnum.md - {"proseWrap":"always"} format 1`] = `
====================================options===================================== ====================================options=====================================
parsers: ["markdown"] parsers: ["markdown"]
@@ -290,20 +320,71 @@ Go
Rust Rust
=====================================output===================================== =====================================output=====================================
日本語、にほんご。汉语, 中文. 日本語,にほんご.English 日本語
words!? 漢字!汉字?「セリフ」(括弧) 文字(括弧)文字【括弧】日本語English 、
にほんご
。
汉语,
中文.
日
本
語
,
に
ほ
ん
ご
.
English words!?
漢字
!
汉字
?
「セリフ」
(括弧)
文字
(括弧)
文字
【括弧】
日本語
English
「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)」「禁則(きんそく)処理(しょり)」「禁則(きんそく)処理(しょり)!!!!」「禁則(きんそく)処理(しょり)!!!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」「禁則(きんそく)処理(しょり)!」 「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)」
「禁則(きんそく)処理(しょり)」
「禁則(きんそく)処理(しょり)!!!!」
「禁則(きんそく)処理(しょり)!!!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
「禁則(きんそく)処理(しょり)!」
中点・中点 中点
・
中点
禁則処理にわざと違反した文章のテストを今から行います。準備はいいでしょうか?レディ、ゴー! 禁則処理にわざと違反した文章のテストを今から行います。準備はいいでしょうか?レデ
ィ、ゴー!
[ウ ィキペディア] [ウ ィキペディア]
[ウ ィキペディア]: https://ja.wikipedia.org/ [ウ ィキペディア]: https://ja.wikipedia.org/
C言語・C++・Go・Rust C言
語
・
C++
・
Go
・
Rust
================================================================================ ================================================================================
`; `;
@@ -0,0 +1,11 @@
English
English
日本語
日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語
日本語
한국어
한국어
中文
中文!
한국어
(中文)
@@ -18,7 +18,10 @@ const snippets = [
// (39 + 40 + 41) * 2 === 40 * 3 * 2 === 40 * 6 // (39 + 40 + 41) * 2 === 40 * 3 * 2 === 40 * 6
// lineWidth is 80 and "文" is double-width // lineWidth is 80 and "文" is double-width
// This is being saved for after Chrome and Safari bug is fixed. // This is being saved for after Chrome and Safari bug is fixed.
output: /* `${"文".repeat(40)}\n`.repeat(6) */ `${"文".repeat(40 * 6)}\n`, get output() {
/* `${"文".repeat(40)}\n`.repeat(6) */
return this.code.replace(/ (?=\n)/g, "");
},
}, },
]; ];
@@ -36,7 +39,13 @@ const snippets = [
name: "Should remove newline around CJ(K) punctuation(-like)", name: "Should remove newline around CJ(K) punctuation(-like)",
code, code,
// Note: replaceAll is not supported in Node 14. // Note: replaceAll is not supported in Node 14.
output: `${code.replace(/\n/g, "")}\n`, // In CSS, a line break between a CJK and a non-CJK character is always interchangeable
// with a space, but Prettier v3 made a design mistake where, in some cases,
// it was treated as equivalent to being deleted
// (see https://github.com/prettier/prettier/issues/14936).
// Therefore, .replace is disabled until it gets fixed in
// https://github.com/prettier/prettier/pull/16805.
output: code, // `${code.replace(/\n/g, "")}\n`,
}); });
} }
@@ -1,7 +1,8 @@
// This preamble is unnecessary after #16805 // This preamble is unnecessary after #16805
const preamble = "a 字 a 字 a 字"; const preamble = "a 字 a 字 a 字";
const code = `${preamble}\n${"葛\u{e0100}\n".repeat(3)}${"𠮷\n".repeat(2)}終\n`; const code = `${preamble}\n${"葛\u{e0100}\n".repeat(3)}${"𠮷\n".repeat(2)}終\n`;
const output = `${preamble}${"葛\u{e0100}".repeat(3)}${"𠮷".repeat(2)}終\n`; // Once browser behavior is standardized across browsers, comment out output and switch to the output below.
// const output = `${preamble}${"葛\u{e0100}".repeat(3)}${"𠮷".repeat(2)}終\n`;
runFormatTest( runFormatTest(
{ {
@@ -10,7 +11,7 @@ runFormatTest(
{ {
code, code,
name: "Make sure non-BMP CJK characters are treated as CJK", name: "Make sure non-BMP CJK characters are treated as CJK",
output, output: code,
}, },
], ],
}, },