跳至主内容区

Prettier 3.4:大量错误修复

· 1 分钟阅读
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

本次发布包含大量错误修复及其他改进。

如果您认可 Prettier 的价值并希望支持我们的工作,请考虑通过 我们的 OpenCollective 直接赞助,或赞助我们依赖的项目如 typescript-eslintremarkBabel。感谢您持续的支持!

其他变更

JavaScript

修复含数组的模板字面量打印问题 (#13315 by @fisker, @syi0808)

// Input
const string = `${[[1, 2], [3, 4]]}`

// Prettier 3.3
const string = `${[
[2],
[4],
]}`;

// Prettier 3.4
const string = `${[
[1, 2],
[3, 4],
]}`;

修复带标签模板字面量中缺失的括号 (#16500 by @syi0808)

// Input
(String?.raw)``;
(getTag?.())``;

// Prettier 3.3
String?.raw``;
getTag?.()``;

// Prettier 3.4
(String?.raw)``;
(getTag?.())``;

不再移除字符串字面量中的无用反斜杠 \ (#16563 by @sosukesuzuki, #16763 by @fisker)

此前,Prettier 会移除字符串字面量中的无用转义字符(\)。然而该行为存在不一致性——模板字面量不适用此规则,该问题已在 issue #16542 中报告。

该功能请求要求将此行为扩展至模板字面量。

经内部讨论,Prettier 团队认为无论是字符串字面量还是模板字面量,移除无用转义字符应是 linter 的职责,而非格式化工具的工作。

此项变更禁用了 Prettier 支持的所有语言中移除字符串字面量无用转义字符的功能。如需保持先前行为,我们推荐使用 ESLint 规则如 no-useless-escape

转义引号(例如 "\"\'")在切换不同引号类型时可能被取消转义。官方文档的设计理念页面对此有详细说明。

// Input
const str = "\a";

// Prettier 3.3
const str = "a";

// Prettier 3.4
const str = "\a";

优化一元表达式中逻辑表达式的注释格式 (#16593 by @sosukesuzuki)

// Input
!(
cond1 || // foo
cond2 || // bar
cond3 // baz
);

// Prettier 3.3
!(
(
cond1 || // foo
cond2 || // bar
cond3
) // baz
);

// Prettier 3.4
!(
cond1 || // foo
cond2 || // bar
cond3 // baz
);

移除对实验性语法的支持 (#16643, #16705 by @fisker)

TypeScript

修复带标签模板字面量中缺失的括号 (#16500 by @syi0808)

// Input
(String?.raw!)``;
(String?.raw)!``;

// Prettier 3.3
String?.raw!``;
String?.raw!``;

// Prettier 3.4
(String?.raw)!``;
(String?.raw)!``;

保留装饰器与被修饰参数属性之间的注释 (#16574 by @sosukesuzuki)

当前版本的 Prettier 会意外移动位于被修饰参数属性(使用 readonlyprivatepublic 等修饰符)与装饰器之间的行注释。这种输出会导致 TypeScript 代码无效。

本次变更确保保留原始格式。

// Input
class Foo {
constructor(
@decorator
// comment
readonly foo,
) {}
}

// Prettier 3.3
class Foo {
constructor(
@decorator
readonly // comment
foo,
) {}
}

// Prettier 3.4
class Foo {
constructor(
@decorator
// comment
readonly foo,
) {}
}

保留修饰符与被装饰属性名之间的注释 (#16578 by @sosukesuzuki)

存在一个缺陷:被装饰属性的修饰符与属性名之间的块注释会被错误地视为装饰器的尾部注释。这种行为不仅不符合预期,还破坏了代码格式的幂等性。

本次变更后,修饰符与属性名之间的块注释位置将得到保留。

// Input
class Foo {
@decorator
readonly /* comment */ propertyName;
}

// Prettier 3.3
class Foo {
@decorator /* comment */
readonly propertyName;
}

// Prettier 3.3 (second output)
class Foo {
@decorator /* comment */ readonly propertyName;
}

// Prettier 3.4
class Foo {
@decorator
readonly /* comment */ propertyName;
}

修复赋值语句中带类型参数的箭头函数多余换行问题 (#16586 by @sosukesuzuki)

存在一个缺陷:当将带有类型参数的链式箭头函数赋值给变量时,如果上方存在行注释,会意外插入多余空行。

本次变更确保不再插入多余空行。

// Input
const foo1 =
// comment
<T,>() => () => 1;

// Prettier 3.3
const foo1 =
// comment

<T,>() =>
() =>
1;

// Prettier 3.4
const foo1 =
// comment
<T,>() =>
() =>
1;

支持"顶层 await 语句" (#16729 by @fisker)

// Input
(await (await fetch()).json()).foo

// Prettier 3.3
await(await fetch()).json().foo;

// Prettier 3.4
(await (await fetch()).json()).foo;

修复类继承即使未超出行宽仍被换行的问题 (#16730 by @fisker)

// Input
export class JiraCreatePixFraudDetectionGateway
implements Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}

// Prettier 3.3
export class JiraCreatePixFraudDetectionGateway
implements
Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}

// Prettier 3.4
export class JiraCreatePixFraudDetectionGateway
implements Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}

类属性中将 declare 置于可访问性修饰符前输出 (#16731 by @fisker)

// Input
class A {
declare private readonly name: string;
}

// Prettier 3.3
class A {
private declare readonly name: string;
}

// Prettier 3.4
class A {
declare private readonly name: string;
}

CSS

修复部分 CSS 内容溢出问题 (#16570 by @seiyab)

/* Input */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131,
U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
}

/* Prettier 3.3 */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
}
}

/* Prettier 3.4 */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
}

移除伪类函数的多余缩进 (#16572 by @sosukesuzuki)

本次变更修复了在伪类函数(如 :where():is():not)的参数列表包含换行时添加多余缩进的缺陷。

/* Input */
:where(input:not([type="button"], [type="reset"], [type="submit"]), textarea, select) {
/* CSS here */
}

/* Prettier 3.3 */
:where(
input:not([type="button"], [type="reset"], [type="submit"]),
textarea,
select
) {
/* CSS here */
}

/* Prettier 3.4 */
:where(
input:not([type="button"], [type="reset"], [type="submit"]),
textarea,
select
) {
/* CSS here */
}

修复不完整 CSS 值注释的格式化问题 (#16583 by @sosukesuzuki, @fisker)

格式化 CSS 值注释时,尾部的 / 可能丢失,导致生成无效注释。

本次变更确保值注释不会被截断。

/* Input */
h1 {
--OFF: /* OFF */;
}

/* Prettier 3.3 */
h1 {
--OFF: /* OFF *;
}

/* Prettier 3.4 */
h1 {
--OFF: /* OFF */;
}

SCSS

修复格式化 SCSS 文件时抛出的错误 (#16607 by @fisker)

// Input
@if true {
$newKey: ($key: ( $theme-name: $value ))
}

// Prettier 3.3
Error

// Prettier 3.4
@if true {
$newKey: (
$key:
(
$theme-name: $value,
),
);
}

修复 SCSS 中注释后尾随逗号位置错误的问题 (#16617 by @Ma-hawaj, @fisker)

/* Input */
$z-indexes: (
header: 1035,
overlay: 1202 // TODO: change to 1050 after bootstrap modals will be removed
);

/* Prettier 3.3 */
$z-indexes: (
header: 1035,
overlay: 1202 // TODO: change to 1050 after bootstrap modals will be removed,
);

/* Prettier 3.4 */
$z-indexes: (
header: 1035,
overlay: 1202, // TODO: change to 1050 after bootstrap modals will be removed
);

HTML

保持非 HTML 文件中的 doctype 声明不变 (#16765 by @fisker)

在 Prettier v3 中,我们将 HTML5 doctype 声明转为小写,这对 HTML 文件是安全的。但用户可能使用 html 解析器格式化其他文件(例如 XHTML),小写化 doctype 会破坏 XHTML 文档。

从 Prettier 3.4 开始,我们仅在文件扩展名为 .html.htm 时小写化 HTML5 doctype (<!doctype html>),其他情况下将保持原样。

<!-- Input -->
<!-- foo.xhtml -->
<!DOCTYPE html>

<!-- Prettier 3.3 -->
<!doctype html>

<!-- Prettier 3.4 -->
<!DOCTYPE html>

Vue

修复 Vue 事件绑定中因非 ASCII 字符导致多余分号的问题 (#16733 by @fisker)

<!-- Input -->
<template>
<button @click="点击事件">点击!</button>
<button @click="onClick">Click!</button>
</template>

<!-- Prettier 3.3 -->
<template>
<button @click="点击事件;">点击!</button>
<button @click="onClick">Click!</button>
</template>

<!-- Prettier 3.4 -->
<template>
<button @click="点击事件">点击!</button>
<button @click="onClick">Click!</button>
</template>

Angular

支持 Angular 19 (#16862 by @fisker)

Angular 19 新增了模板表达式中 typeof 关键字的支持

<!-- Input -->
<div>{{ typeof
x ===
'object' ? 'Y' : 'N'}}</div>

<!-- Prettier 3.3 -->
<div>
{{ typeof
x ===
'object' ? 'Y' : 'N'}}
</div>

// Prettier 3.4
<div>{{ typeof x === "object" ? "Y" : "N" }}</div>

Markdown

移除 Markdown 无序列表中行前缀后的多余空格 (#15526 by @TomasLudvik)

<!-- Input -->
- first line
- second line indented
- third line
- fourth line
- fifth line

<!-- Prettier 3.3 -->
- first line
- second line indented
- third line
- fourth line
- fifth line

<!-- Prettier 3.4 -->
- first line
- second line indented
- third line
- fourth line
- fifth line

修复含链接引用的句子中错误换行问题 (#16546 by @seiyab)

<!-- Input (--prose-wrap=always) -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To run them, install VHS from main (the theme and screenshot commands are not yet released).

<!-- Prettier 3.3 -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To run
them, install VHS from main (the theme and screenshot commands are not yet released).

<!-- Prettier 3.4 -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To
run them, install VHS from main (the theme and screenshot commands are not yet
released).

保留行尾和下行行首的非 ASCII 空白字符 (#16619 by @tats-u)

Prettier 会移除行尾和下行行首的非 ASCII 空格,但此行为不符合 CommonMark 规范。

https://spec.commonmark.org/0.31.2/#soft-line-breaks

行尾和下行行首的空格会被移除:

https://spec.commonmark.org/0.31.2/#unicode-whitespace-character

Unicode 空白字符指 Unicode Zs 通用类别中的字符,或制表符 (U+0009)、换行符 (U+000A)、换页符 (U+000C)、回车符 (U+000D)。

Unicode 空白指一个或多个 Unicode 空白字符的序列。

空格指 U+0020。

CommonMark 规范未提及非 ASCII 空格,因此移除它们会改变 Markdown 文档的内容。

<!-- Input -->
 EM Space (U+2003) EM Space

 全角スペース (U+3000) 全形空白

<!-- Prettier 3.3 -->
EM Space (U+2003) EM Space

全角スペース (U+3000) 全形空白

<!-- Prettier 3.4 -->
 EM Space (U+2003) EM Space

 全角スペース (U+3000) 全形空白

禁止在中文/日文与其他字符之间换行 (#16691 by @tats-u)

Markdown 文档主要会被转换为 HTML 或基于 JavaScript 框架的组件。这意味着 Markdown 中的段落最终会由浏览器根据 CSS 规则处理。因为许多 Markdown 转换器会保留输入 Markdown 段落中的换行符,而 HTML 本身并未规定浏览器应如何处理 HTML 文本中的换行。

根据 CSS 规则(CSS 文本模块第 3 级及更高版本),浏览器应删除中日文字符之间的换行符,而不是用空格替换。然而长期以来,基于 WebKit 或衍生自 WebKit 的浏览器(Chrome、Safari 等)都忽略了此规则。

例如以下 HTML 段落:

<p>
日本語
汉语
漢語
<p>

由以下 Markdown 生成:

日本語
汉语
漢語

根据 CSS 规则应呈现为以下效果(实际在 Firefox 中确实如此):

日本語汉语漢語

但 Chrome 和 Safari 会呈现为:

日本語 汉语 漢語

因此我们需要阻止 Prettier 在中日文字符之间换行。我们决定不再要求用户通过插件(如 remark-join-cjk-lines)来连接以中日文字符开头或结尾的行。

另外,根据 CSS 文本模块第 3 版(在该提交注释掉具体规则前修复了CSS工作组草案问题),中日文字符与其他字符间的换行等同于空格。Firefox 遵循此规则,因此所有浏览器都会将以下段落:

<!-- prettier-ignore -->
```html
<p>日本語 English 汉语 한국어 漢語</p>

<p></p>

呈现为:

日本語 English 汉语 한국어 漢語

然而 Prettier 长期以来在 Markdown 中会对中日文字符间换行,且自 3.0.0 起在某些情况下会对中日文字符与拉丁字符间换行。例如以下 Markdown 段落:

日本語English汉语
English
漢語

当 Prettier 3.x 中设置 --prose-wrap 为非 preserve 值时,会被格式化为:

日本語English汉语English漢語

但基于此 Markdown 生成的 HTML:

<p>
日本語English汉语
English
漢語
</p>

在所有浏览器中均呈现为:

日本語English汉语 English 漢語

因此我们还需阻止 Prettier 在 Markdown 中对中日文字符周围换行。未来版本中我们将使 Prettier 行为符合此规则,此后中日文字符与其他字符间的换行将在特定规则下被允许。

少数例外之一是中日文字符与韩文字符间的空格和换行。以下 Markdown 段落在当前 Prettier 版本中等效:

현재 韓國의 大統領은 尹錫悅이다.
현재
韓國의
大統領은
尹錫悅이다.

后者在 --prose-wrap=always 配合足够长的 --print-width--prose-wrap=never 时会格式化为前者;而前者在 --prose-wrap=always 配合极短 --print-width 时会格式化为后者。因此我们无需处理此类空格和换行。

另一例外是中日文字符与 Markdown 中有意义的符号(如 *`[])间的处理。例如以下 Markdown 段落在当前版本同样等效:

**Yarn** のCLI経由でフォーマットするには `yarn prettier -w ` を実行してください。
**Yarn**
のCLI経由でフォーマットするには
`yarn prettier -w .`
を実行してください。
<!-- Input (--prose-wrap=always --print-width=20) -->
日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어

日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어

<!-- Prettier 3.3 -->
日本語 汉语 漢語
English 한국어 日本
語 汉语 漢語 English
한국어 日本語 汉语
漢語 English 한국어
日本語 汉语 漢語
English 한국어

日本語汉语漢語
English한국어日本語
汉语漢語
English한국어日本語
汉语漢語
English한국어日本語
汉语漢語
English한국어

<!-- Prettier 3.4 -->
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어

日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어

通知 regexp-util 生成兼容 u 标志的正则表达式 (#16816 by @tats-u)

位于基本多文种平面(BMP)之外的 CJK 字符以及表意变体序列(IVS;专门用于汉字/日文汉字的变体选择符),在 JavaScript 字符串中占用 2 个字符,但此前未被 Prettier 视为 CJK 字符处理。这是因为 Prettier 没有向 regexp-util 包传递正确的 "u" 标志。

以下示例中,"𠮷"(U+20BB7)位于 BMP 之外,而"葛󠄀"是由 BMP 中的汉字"葛"(U+845B)和 IVS U+E0100 组合而成。后者需要支持 Adobe Japan-1 的字体(例如 Yu Gothic UI 和 Source Han Sans)才能渲染为与不带 IVS 的字符(葛)不同的形态。

<!-- Input (--prose-wrap=never) -->
a 字 a 字 a 字
𠮷
𠮷
葛󠄀
葛󠄀


<!-- Prettier 3.3 -->
a 字 a 字 a 字 𠮷 𠮷 葛󠄀 葛󠄀 終

<!-- Prettier 3.4 -->
a 字 a 字 a 字𠮷𠮷葛󠄀葛󠄀終

YAML

修复非单行项中 YAML 注释在 # 前缺少空格的问题 (#16489 by @fyc09)

# Input
123: # hello
# comment

# Prettier 3.3
123:# hello
# comment

# Prettier 3.4
123: # hello
# comment

API

停止在 prettier.doc.printDocToString 中修改文档对象 (#13315 by @fisker)

出于性能考虑,prettier.doc.printDocToString 过去会在打印过程中修改 fill 命令的 .parts 属性。现已改为使用纯函数实现,确保输出正确性。

公开 getPreferredQuote 函数 (#16567 by @sosukesuzuki)

此项变更将内部函数 getPreferredQuote 纳入公共 API。

在 JavaScript 等语言中,字符串字面量可使用单引号或双引号。Prettier 根据字符串内的引号数量以及 singleQuote 选项值决定包裹字符串的引号类型。更多细节请参阅设计理念文档。

getPreferredQuote 函数用于确定字符串字面量的合适引号,其接口如下:

type Quote = '"' | "'";
function getPreferredQuote(
text: string,
preferredQuoteOrPreferSingleQuote: Quote | boolean,
): Quote;

以下是一些使用示例:

import * as prettier from "prettier";

const SINGLE_QUOTE = `'`;
const DOUBLE_QUOTE = `"`;

console.log(prettier.util.getPreferredQuote(`Hello World Test`, SINGLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`Hello World Test`, DOUBLE_QUOTE)); // "
console.log(prettier.util.getPreferredQuote(`'Hello' "World" 'Test'`, SINGLE_QUOTE)); // "
console.log(prettier.util.getPreferredQuote(`"Hello" 'World' "Test"`, DOUBLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`"Hello" "World" "Test"`, SINGLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`'Hello' 'World' 'Test'`, DOUBLE_QUOTE)); // "

公开此函数将使插件开发者受益。由于该函数实现较为简洁,您可在源代码中查看具体细节。

修复 Node.js 23 中加载 ESM 风格共享配置文件的问题 (#16857 by @sosukesuzuki)

在 Prettier 3.3 中,尝试在 Node.js 23 加载 ESM 风格的共享配置文件会导致如下警告,致使配置选项无法加载:

[warn] Ignored unknown option { __esModule: true }.
[warn] Ignored unknown option { default: { trailingComma: "es5", tabWidth: 4, singleQuote: true } }.

此问题由 Node.js 23 的新模块特性 require(ESM) 导致。Prettier 3.4 已解决此问题,确保配置选项可正常加载。

更多细节请参阅:https://github.com/prettier/prettier/issues/16812。

CLI

忽略 Jujutsu 目录中的文件 (#16684 by @marcusirgens)

Jujutsu 版本控制系统使用 .jj 目录,类似于 Git 使用 .git 的方式。

此项更改将 .jj 添加到 Prettier 静默忽略的目录列表中。

其他改进

修复 cursorOffset 功能有时极慢的问题 (#15709 by @ExplodingCabbage)

此前,Prettier 的 cursorOffset 功能在某些不利情况下会变得异常缓慢(即当用户的光标不在 AST 的叶子节点内,且包含它的非叶子节点非常大并被 Prettier 大幅重新格式化时)。因此,如果你通过底层使用 cursorOffset 的编辑器集成来使用 Prettier,那么在尝试格式化文件时,你的编辑器有时会莫名其妙地挂起。

目前已知的所有此类现象示例都应已修复,但我们欢迎任何其他病态示例的错误报告。