Prettier 3.9:重大解析器升级与格式化改进
我们很高兴地宣布 Prettier 3.9 发布!
本次更新为 Markdown、YAML、Flow、GraphQL 和 Angular 带来了重大解析器升级,同时改进了 JavaScript 和 TypeScript 的代码格式化(尤其是 --no-semi 模式)。
如果你认可 Prettier 的价值,请考虑在 OpenCollective 上赞助我们,或支持我们依赖的开源项目。你的贡献帮助我们持续改进工具。
感谢大家的一路支持!❤️
再次提醒:安装或更新 Prettier 后,强烈建议在 package.json 中锁定确切版本号:"prettier": "3.9.0",而不是 "prettier": "^3.9.0"。
如果你使用 @prettier/plugin-oxc 或 @prettier/plugin-hermes,别忘了同步升级这些插件,以确保应用新的格式化规则。
亮点
Markdown
解析器升级至最新版 micromark(#18277,由 @seiyab、@j-f1、@fisker 提交)
我们将 Prettier 的 Markdown 解析器从过时的 remark-parse v8 升级到了更现代的 micromark v4。这次升级显著提升了对 CommonMark 和 GFM 规范的兼容度,修复了大量长期存在的解析问题,也为后续改进打下了坚实基础。
衷心感谢 @seiyab、@j-f1 以及所有为此做出贡献的人,推动这项备受期待的升级得以实现!
注意:尽管核心 Markdown 解析器已升级,但 MDX 解析器的升级尚未完成。如果你熟悉 unified 生态、micromark 或 MDX,欢迎帮忙完成迁移 —— 我们非常期待你的贡献!
YAML
将依赖项 yaml 更新至 v2 (#18419,@ota-meshi、@fisker 贡献)
YAML 解析器已升级至 yaml v2,修复了许多长期存在的解析问题。
感谢 @ota-meshi 在 yaml-unist-parser 方面所做的出色工作。
GraphQL
支持 GraphQL.js v17 (#19171,@YBJ0000 贡献;#19297,@fisker 贡献)
Prettier 现已完整支持 GraphQL.js v17 中的新语法特性,包括指令定义上的指令、片段参数及其他增强功能。
# 输入
fragment variableProfilePic on User {
...dynamicProfilePic(size: $size)
}
# Prettier 3.8
SyntaxError: Syntax Error: Expected Name, found "(". (2:23)
# Prettier 3.9
fragment variableProfilePic on User {
...dynamicProfilePic(size: $size)
}
指令定义上的指令(以及 extend directive 扩展)
# 输入
directive @a @b on QUERY
extend directive @a @b
# Prettier 3.8
Error: Syntax Error: Expected "on", found "@".
# Prettier 3.9
directive @a @b on QUERY
extend directive @a @b
Flow
新的基于 Rust 的 Flow 解析器 (#19398,@SamChou19815 贡献)
Prettier 现已采用 Flow 团队发布的基于 Rust 的新解析器(oxidized),提升了 Flow 类型代码的性能。
在本地纯解析器基准测试中,新解析器解析 Prettier 的有效 Flow fixtures 中位耗时 266.4ms,旧解析器为 422.6ms;解析 flow_parser.js 耗时 1298.0ms,旧版为 2269.6ms。
其他变更
JavaScript
稳定 no-semi 模式下 break 和 continue 周围的注释处理(#7161 by @thorn0, @fisker)
// 输入
for (;;) {
if (condition) {
break; // 退出注释
(possibleArray || []).sort()
}
}
// Prettier 3.8 (--no-semi, 第一次格式化)
for (;;) {
if (condition) {
break // 退出注释
;(possibleArray || []).sort()
}
}
// Prettier 3.8 (--no-semi, 第二次格式化)
for (;;) {
if (condition) {
break // 退出注释
;(possibleArray || []).sort()
}
}
// Prettier 3.9 (--no-semi, 两种格式均可)
for (;;) {
if (condition) {
break; // 退出注释
(possibleArray || []).sort();
}
}
移除 return 语句中的冗余括号(#18142 by @fisker)
// 输入
function sequenceExpressionInside() {
return ( // a 的原因
a, b
);
}
// Prettier 3.8
function sequenceExpressionInside() {
return (
// a 的原因
(a, b)
);
}
// Prettier 3.9
function sequenceExpressionInside() {
return (
// a 的原因
a, b
);
}
修复嵌入模板插值的对齐问题(#18380 by @fisker)
// 输入
string = `
.class {
flex-direction: column${
long_cond && long_cond && long_cond
? "-reverse" :
""
};
}
`;
css = css`
.class {
flex-direction: column${
long_cond && long_cond && long_cond
```html
? "-reverse" :
""
};
}
`;
// Prettier 3.8
string = `
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;
css = css`
.class {
flex-direction: column${long_cond && long_cond && long_cond
? "-reverse"
: ""};
}
`;
// Prettier 3.9
string = `<
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;
css = css`
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;
避免在嵌入的模板插值中产生换行(#18380,@fisker 提交)
// 输入
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
// Prettier 3.8
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>
${long_cond && long_cond && long_cond && long_cond && long_cond
? "content"
: ""}
</div>
`;
// Prettier 3.9
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>
${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}
</div>
`;
改进逻辑非表达式的打印(#18397、#18401,@fisker 提交)
```- 修复某些情况下内部逻辑表达式可能被双重括号化的问题。
- 将
if/while/do..while条件中的表达式内联,以在将条件改为否定形式时减少差异。
// 输入
if (!(
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}
// Prettier 3.8
if (
!(
// `import("foo")`
(
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)
)
) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}
// Prettier 3.9
if (!(
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}
上述代码示例是 Prettier 代码库中真实存在的情况
移除 CSS 选择器中表达式前意外插入的空格 (#18460 by @kovsu)
// 输入
css`foo:${bar} {}`
// Prettier 3.8
css`
foo: ${bar} {
}
`;
// Prettier 3.9
css`
foo:${bar} {
}
`;
保留 IIFE 函数注释在括号内 (#18538 by @fisker)
// 输入
const a = (
/**
* @param {number} foo
* @return {number}
*/
function (foo) {
return foo + 1;
}
)(1);
// Prettier 3.8
const a = /**
* @param {number} foo
```js
/**
* @param {number} foo
* @return {number}
*/
(function (foo) {
return foo + 1;
})(1);
// Prettier 3.9
const a = (
/**
* @param {number} foo
* @return {number}
*/
function (foo) {
return foo + 1;
}
)(1);
```
修复带括号调用者的注释打印 (#18540 by @fisker)
// 输入
const a = (
function(){}
/* callee 的注释 */
)(),
b = (
function(){}
)(/* call 的注释 */
// Prettier 3.8
const a = (function () {})(),
/* callee 的注释 */
b = (function () {})(/* call 的注释 */);
// Prettier 3.9
const a = (
function () {}
/* callee 的注释 */
)(),
b = (function () {})(/* call 的注释 */);
```
在 JSDoc 中保留尾部空格 (#18594 by @seiyab)
部分工具会将这些空格视为有意义的内容。
await prettier.format("/**\n * With 2 spaces \n */", { parser: "babel" });
/* Prettier 3.8 */
// -> '/**\n * With 2 spaces\n */\n'
/* Prettier 3.9 */
// -> '/**\n * With 2 spaces \n */\n'
// ^^ 保留空格
```
弃用旧的 "import assertions" 语法支持 (#18611 by @fisker)
import assertions(使用 assert 关键字)是当前 import attributes 提案 的旧版本。
// 旧版(已弃用)
import foo from "./foo.json" assert { type: "json" };
// 当前标准
import foo from "./foo.json" with { type: "json" };
```
Babel 8 已完全移除对旧版 assert 语法的支持(旧的 parser 插件已删除)。
失去 Babel parser 支持后,Prettier 无法再可靠地解析或格式化使用 import ... assert { ... } 的代码。
请迁移至 with 语法。
- import foo from "./foo.json" assert { type: "json" };
+ import foo from "./foo.json" with { type: "json" };
参见 Prettier 关于非标准语法的免责声明。
改进空调用参数列表周围的注释处理 (#18615 by @fisker)
// 输入
call
// 行注释
();
call(// 行注释
);
call(
// 行注释
);
call
/* 块注释 */
();
call(/* 块注释 */
);
call(
/* 块注释 */
);
// Prettier 3.8
call();
// 行注释
call(); // 行注释
call();
// 行注释
call();
/* 块注释 */
call /* 块注释 */();
call();
/* 块注释 */
// Prettier 3.9
call
// 行注释
();
call(
// 行注释
);
call(
// 行注释
);
call
/* 块注释 */
();
call(/* 块注释 */);
call(/* 块注释 */);
修复调用表达式中不稳定的注释问题 (#18615 by @fisker)
// 输入
foo =
call(
/* 调用参数 */);
// Prettier 3.8(第一次格式化)
foo =
call();
/* 调用参数 */
// Prettier 3.8(第二次格式化)
foo = call();
/* 调用参数 */
// Prettier 3.9
foo = call(/* 调用参数 */);
允许包含长注释的空调用表达式参数列表换行 (#18615 by @fisker)
// 输入
call(/* 这是一个非常非常非常非常非常非常非常非常非常非常长的注释 */);
array = [/* 这是一个非常非常非常非常非常非常非常非常非常非常长的注释 */];
object = {/* 这是一个非常非常非常非常非常非常非常非常非常非常长的注释 */};
// Prettier 3.8
call(/* 这是一个非常非常非常非常非常非常非常非常非常非常长的注释 */);
array = [
/* 这是一个非常非常非常非常非常非常非常非常非常非常长的注释 */
];
object = {
```javascript
/* a long long long long long long long long long long long long comment */
};
// Prettier 3.9
call(
/* a long long long long long long long long long long long long comment */
);
array = [
/* a long long long long long long long long long long long long comment */
];
object = {
/* a long long long long long long long long long long long long comment */
};
```
短注释不再强制空数组和对象换行 (#18617 by @fisker)
```javascript
// Input
const array = [/* comment */]
const object = {/* comment */}
// Prettier 3.8
const array = [
/* comment */
];
const object = {
/* comment */
};
// Prettier 3.9
const array = [/* comment */];
const object = {/* comment */};
```
使用行注释或多行注释仍可强制数组和对象换行。
```javascript
const array = [
// comment
];
const object = {
// comment
};
const array2 = [
/*
comment
*/
];
const object2 = {
/*
comment
*/
};
```
修复函数参数中悬空注释的打印问题 (#18623 by @fisker)
```javascript
// Input
fn = function(
// comment
) {}
arrow = (
// comment
) => {}
// Prettier 3.8
fn = function () // comment
{};
arrow = () =>
// comment
{};
// Prettier 3.9
fn = function (
// comment
) {};
arrow = (
// comment
) => {};
```
修复 NewExpression 与 CallExpression 之间注释附加不一致的问题 (#18669 by @fisker)
```javascript
// Input
foo( // comment
bar
);
new Foo( // comment
bar
);
// Prettier 3.8
foo(
// comment
bar,
);
new Foo(bar); // comment
// Prettier 3.9
foo(
// comment
bar,
);
new Foo(
// comment
bar,
);
```
修复可选链周围缺失的括号(#18720,@fisker)
// 输入
(a?.b()).c();
// Prettier 3.8
a?.b().c();
// Prettier 3.9
(a?.b()).c();
修复变量声明的范围格式化(#18734,#18740,@fisker)
// 输入
let i ="format me!" ;
// ^^^^^^^^^^^^^^ 范围
// Prettier 3.8
let i = "format me!"; ;
// Prettier 3.9
let i = "format me!";
改进语句间空行检测(#18736,@fisker)
// 输入
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray()
;
foo();
// Prettier 3.8 (--no-semi)
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray();
foo();
// Prettier 3.9
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray();
foo();
在 no-semi 模式下保留 ; 前的空行(#18736,#18737,@fisker)
// 输入
a
;[].b()
// Prettier 3.8 (--no-semi)
a
;[].b()
// Prettier 3.9
a
;[].b()
类型转换注释前打印前导分号(#18751,@fisker)
// 输入
;/** @type {string[]} */ (['foo', 'bar']).forEach(doStuff)
# Prettier 3.9:主要解析器升级与格式化改进 · Prettier
---
## 解析器升级
这次更新引入了一些重大的底层解析器改进,主要解决了几个长期存在的问题。
**支持 ECMAScript 2025 可选链赋值**([#18735](https://github.com/prettier/prettier/pull/18735),[fisker](https://github.com/fisker) 贡献)
现在 Prettier 可以直接处理 `?. &&=`、`?. ||==` 这类语法糖,不再需要在 AST 上做任何特殊转换。
```javascript
// 之前会被拒绝的写法,现在直接格式化
foo?.bar &&= 1
foo?.bar ||= 1
foo?.bar ??= 1
```
---
**修复可选链调用前的分号**([#18774](https://github.com/prettier/prettier/pull/18774),[fisker](https://github.com/fisker) 贡献)
当一行同时有 JSDoc 类型注解和可选链调用时,之前解析器会把它们错误地合并。现在修复后行为正确。
```javascript
// Input
/** @type {string[]} */ ;(["foo", "bar"]).forEach(doStuff)
// Prettier 3.8 (--no-semi, 第一次格式化)
/** @type {string[]} */ ;(["foo", "bar"]).forEach(doStuff)
// Prettier 3.8 (--no-semi, 第二次格式化)
/** @type {string[]} */ ;["foo", "bar"].forEach(doStuff)
// Prettier 3.9 (--no-semi)
;/** @type {string[]} */ (["foo", "bar"]).forEach(doStuff)
```
---
**防止箭头函数参数后的注释被错误移动**([#18775](https://github.com/prettier/prettier/pull/18775),[fisker](https://github.com/fisker) 贡献)
当注释紧跟在箭头前面的参数之后时,3.8 会把它挪到参数内部或外面,导致语义混乱。现在注释会保持在原来的位置。
```javascript
// 输入
KEYPAD_NUMBERS.map(num => ( // Buttons 0-9
));
const createIdFilter =
(foo) => /** @param {string} id */
(id) => id;
// Prettier 3.8
KEYPAD_NUMBERS.map(
(
num // Buttons 0-9
) =>
);
const createIdFilter = (foo /** @param {string} id */) => (id) => id;
// Prettier 3.9
KEYPAD_NUMBERS.map((num) => (
// Buttons 0-9
));
const createIdFilter = (foo) => /** @param {string} id */ (id) => id;
```
---
**修复 `else` 上方的注释位置**([#18813](https://github.com/prettier/prettier/pull/18813),[fisker](https://github.com/fisker) 贡献)
之前当 `if` 块为空、且后面跟着注释 + `else if` 时,注释会被错误地归到 `if` 块内部。现在注释会正确地留在 `else` 上方。
```javascript
// 输入
/* Case 1 */
if (1) {
}
/* Case 2 */
else if (2) {
}
// Prettier 3.8
/* Case 1 */
if (1) {
} else if (2) {
/* Case 2 */
}
// Prettier 3.9
/* Case 1 */
if (1) {
}
/* Case 2 */
else if (2) {
}
```
---
**修复 debugger 语句中的"注释未打印"错误**([#18840](https://github.com/prettier/prettier/pull/18840),[fisker](https://github.com/fisker) 贡献)
`debugger` 语句后跟分号时,注释会被吞掉并抛出错误。现在已修复。
```javascript
// 输入
debugger // Comment
;
// Prettier 3.8
Error: Comment "comment" was not printed. Please report this error!
// Prettier 3.9
debugger; // Comment
```
---
**改进 `no-semi` 模式下 `do..while` 的格式化输出**([#18851](https://github.com/prettier/prettier/pull/18851),[fisker](https://github.com/fisker) 贡献)
当禁用分号时,`do..while` 后面紧跟其他语句的情况现在能正确处理,不再出现多余的分号或格式错乱。
```javascript
// 输入
do {
doStuff()
} while (1)
// comment
;[foo, bar].forEach(doStuff)
```
```javascript
doStuff()
} while (
1
// comment
)
;[foo, bar].forEach(doStuff)
// Prettier 3.9
do {
doStuff()
} while (1)
// comment
;[foo, bar].forEach(doStuff)
```
修复 experimentalTernaries 空分支中重复的注释 (#18963,@kovsu 提交)
// 输入
condition ? ifTrue
: [
// Hello, world!
];
// Prettier 3.8(--experimental-ternaries)
condition ? ifTrue
// Hello, world!
: (
[
// Hello, world!
]
);
// Prettier 3.9(--experimental-ternaries)
condition ? ifTrue : (
[
// Hello, world!
]
);
保留 JSX 属性之间的空行 (#19161,@Poliklot 提交)
Prettier 现在会在 JSX 属性之间保留一条现有空行,与它在其他多处保留空行的行为保持一致。多个连续空行仍会被合并为一行。
// 输入
<Button
type="submit"
variant="primary"
size="large"
disabled={isSubmitting}
onClick={handleSubmit}
/>;
// Prettier 3.8
<Button
type="submit"
variant="primary"
size="large"
disabled={isSubmitting}
onClick={handleSubmit}
/>;
// Prettier 3.9
<Button
type="submit"
variant="primary"
size="large"
disabled={isSubmitting}
onClick={handleSubmit}
/>;
修复赋值语句中多行 JSDoc 风格注释的对齐问题 (#19180,@kovsu 提交)
// 输入
const foo = /**
* comment
*/ bar;
// Prettier 3.8
const foo = /**
* comment
*/ bar;
// Prettier 3.9
const foo =
/**
* comment
*/ bar;
移除缺少 `update` 表达式的 for 语句中的多余空格 (#19188,@Poliklot 提交)
修复箭头函数序列表达式体的不稳定注释 (#19253 by @itsybitsybootsy)
// 输入
for (initialize;;) {}
for (; condition;) {}
// Prettier 3.8
for (initialize; ; ) {}
for (; condition; ) {}
// Prettier 3.9
for (initialize; ;) {}
for (; condition;) {}
修复箭头函数序列表达式体的不稳定注释 (#19253 by @itsybitsybootsy)
// 输入
const fn = () => (a, b, c /* abc */);
// Prettier 3.8
const fn = () => (a, b, c) /* abc */;
// Prettier 3.9
const fn = () => (a, b, c /* abc */);
修复箭头函数赋值表达式体的不稳定注释 (#19262 by @itsybitsybootsy)
// 输入
const fn = () => (a = b /* abc */);
// Prettier 3.8
const fn = () => (a = b) /* abc */;
// Prettier 3.9
const fn = () => (a = b /* abc */);
保留序列或赋值表达式括号内的尾部注释 (#19263 by @sarathfrancis90)
// 输入
const x = (a, b, c /* comment */);
const y = (a = b /* comment */);
// Prettier 3.8
const x = (a, b, c) /* comment */;
const y = (a = b) /* comment */;
// Prettier 3.9
const x = (a, b, c /* comment */);
const y = (a = b /* comment */);
改进对象中注释的格式 (#19310 by @fisker)
// 输入
const P = {/** X */
Y: "z",
}
// Prettier 3.8
const P = {
/** X */ Y: "z",
};
// Prettier 3.9
const P = {
/** X */
Y: "z",
};
TypeScript
修复成员链中的换行问题 (#17251 by @fisker)
// 输入
model = types
```html
.model({ something: mxSomething })
.volatile<| "a" | "b">(() => [
annularThingamabobWrapper,
octahedralWeevilService,
cantileveredRhubarbProcessor,
annularPlatypusGenerator,
]);
// Prettier 3.8
model = types
.model({ something: mxSomething })
.volatile<
"a" | "b"
>(() => [annularThingamabobWrapper, octahedralWeevilService, cantileveredRhubarbProcessor, annularPlatypusGenerator]);
// Prettier 3.9
model = types
.model({ something: mxSomething })
.volatile<"a" | "b">(() => [
annularThingamabobWrapper,
octahedralWeevilService,
cantileveredRhubarbProcessor,
annularPlatypusGenerator,
]);
修复带类型转换的新表达式格式问题 (#18507 by @fisker)
// 输入
new (require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P)({
rootDir: dir,
});
(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});
// Prettier 3.8
new (require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P)({
rootDir: dir,
});
(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});
// Prettier 3.9
new (
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});
(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});
修复行注释越过成员表达式查找 (#18522 by @ulrichstark)
成员表达式对象与属性之间的行注释会被错误地移到 as 表达式等后续内容之后。
// 输入
function getClassNameFromPrototypeMethod(container) {
return ((container // a
.left as PropertyAccessExpression) // b
```html
.expression as PropertyAccessExpression) // c
.expression; // d
}
// Prettier 3.8
function getClassNameFromPrototypeMethod(container) {
return (
(
container.left as PropertyAccessExpression // a
).expression as PropertyAccessExpression // b
).expression; // c // d
}
// Prettier 3.9
function getClassNameFromPrototypeMethod(container) {
return (
(
container // a
.left as PropertyAccessExpression
) // b
.expression as PropertyAccessExpression
) // c
.expression; // d
}
修复箭头函数格式化不一致的问题(#18589 by @fisker)
// 输入
a.map(() => ({
name,
}));
a.map(():A => ({
name,
}));
a.map(():{A} => ({
name,
}));
// Prettier 3.8
a.map(() => ({
name,
}));
a.map(
(): A => ({
name,
}),
);
a.map((): { A } => ({
name,
}));
// Prettier 3.9
a.map(() => ({
name,
}));
a.map((): A => ({
name,
}));
a.map((): { A } => ({
name,
}));
改进类型参数内注释的打印效果(#18619 by @fisker)
// 输入
foo<
// Comment
Type
>(
// Comment
value
)
foo<
Type // Comment
>(
)
// Prettier 3.8
foo<// Comment
Type>(
// Comment
value,
);
foo<Type>(); // Comment
// Prettier 3.9
foo<
// Comment
Type
>(
// Comment
value,
);
foo<
Type // Comment
>();
修复条件类型注释缩进问题(#18644 by @lindsaycode05)
// 输入
type T = any extends B
// Comment
// Multiline comment
? undefined | NonNullable<B>[foo]
: B[foo];
// Prettier 3.8
type T = any extends B
? // Comment
// Multiline comment
```
undefined | NonNullable[foo]
: B[foo];
// Prettier 3.9
type T = any extends B
? // 注释
// 多行注释
undefined | NonNullable[foo]
: B[foo];
修复 class superClass 格式化不一致的问题 (#18652, #18659 by @fisker)
// 输入
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz! {}
// Prettier 3.8
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName extends (foo
.bar.Baz!) {}
// Prettier 3.9
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz! {}
统一打印非空断言 (#18654 by @fisker)
// 输入
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);
// Prettier 3.8
const corners = baseCorners.map(
(corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);
// Prettier 3.9
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);
// 输入
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id));
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id)!);
// Prettier 3.8
```typescript
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id),
);
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id)!);
// Prettier 3.9
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id),
);
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id)!,
);
```
保留可选链周围的非空断言 (#18661 by @fisker)
// 输入
(a?.b!).c;
(a?.b)!.c;
(a?.b!)!.c;
// Prettier 3.8
(a?.b)!.c;
(a?.b)!.c;
a?.b!!.c;
// Prettier 3.9
(a?.b!).c;
(a?.b)!.c;
(a?.b!)!.c;
打印枚举键时尊重 quoteProps 选项 (#18700, #18703 by @fisker)
// 输入
enum E {
A1 = 0,
"A2" = 1,
"A-3" = 2,
}
// Prettier 3.8
enum E {
A1 = 0,
"A2" = 1,
"A-3" = 2,
}
// Prettier 3.9 (--quote-props=as-needed)
enum E {
A1 = 0,
A2 = 1,
"A-3" = 2,
}
// Prettier 3.9 (--quote-props=consistent)
enum E {
"A1" = 0,
"A2" = 1,
"A-3" = 2,
}
方法签名中尊重 quoteProps (#18702 by @fisker)
// 输入
type T = {
method1(): string;
"method2"(): string;
"method-3"(): string;
};
// Prettier 3.8
type T = {
method1(): string;
"method2"(): string;
"method-3"(): string;
};
// Prettier 3.9 (--quote-props=as-needed)
type T = {
method1(): string;
method2(): string;
"method-3"(): string;
};
// Prettier 3.9 (--quote-props=consistent)
type T = {
"method1"(): string;
"method2"(): string;
"method-3"(): string;
};
保持映射类型中的内联注释(#18731 by @fisker)
// 输入
type B = {
/* comment */ [b in B]: string
};
// Prettier 3.8
type B = {
/* comment */
[b in B]: string;
};
// Prettier 3.9
type B = {
/* comment */ [b in B]: string;
};
在 no-semi 模式下类型断言前打印分号(#18738 by @fisker)
// 输入
a;
c;
// Prettier 3.8 (--no-semi, 首次格式化)
a
c
// Prettier 3.8 (--no-semi, 二次格式化)
a < b > c
// Prettier 3.9 (--no-semi)
a
;c
在类型参数约束中对条件类型添加括号(#18760 by @kovsu)
// 输入
const foo = () => true;
// Prettier 3.8
const foo = () => true;
// Prettier 3.9
const foo = () => true;
修复联合类型最后一个操作数的注释打印问题(#18798 by @fisker)
// 输入
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) & Bar; // Final comment2
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) | Bar; // Final comment2
// Prettier 3.8
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) &
Bar; // Final comment2
type Foo2 =
| (
| "thing1" // Comment1
| "thing2"
) // Comment2
| Bar; // Final comment2
// Prettier 3.9
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) &
Bar; // Final comment2
type Foo2 =
| (
| "thing1" // Comment1
| "thing2" // Comment2
)
| Bar; // 末尾注释2
当一行能放下时不换行联合类型 (#18827 by @fisker)
// 输入
type Browser = "chromium" | "webkit" | "firefox" | "chromium-msedge" | "chromium-chrome";
// Prettier 3.8
type Browser =
| "chromium"
| "webkit"
| "firefox"
| "chromium-msedge"
| "chromium-chrome";
// Prettier 3.9
type Browser =
"chromium" | "webkit" | "firefox" | "chromium-msedge" | "chromium-chrome";
移除条件类型中联合类型的多余缩进 (#18827 by @fisker)
// 输入
type A = T extends "arrow"
? ExcalidrawArrowElement["startBinding"] | ExcalidrawElbowArrowElement["startBinding"]
: never;
// Prettier 3.8
type A = T extends "arrow"
?
| ExcalidrawArrowElement["startBinding"]
| ExcalidrawElbowArrowElement["startBinding"]
: never;
// Prettier 3.9
type A = T extends "arrow"
? | ExcalidrawArrowElement["startBinding"]
| ExcalidrawElbowArrowElement["startBinding"]
: never;
对齐联合类型元素前的 JSDoc 注释 (#18833 by @fisker)
// 输入
type A =
|
/**
* 注释
*/
a
| (b | c);
type A2 =
|
// 注释
a
| (b | c);
// Prettier 3.8
type A =
| /**
* 注释
*/
a
| (b | c);
type A2 =
| // 注释
a
| (b | c);
// Prettier 3.9
type A =
| /**
* 注释
*/
a
| (b | c);
type A2 =
| // 注释
a
| (b | c);
防止类属性中的注释被移动 (#18837 by @fisker)
// 输入
class foo {
bar () /* bar */;
baz /* baz */ ();
}
// Prettier 3.8
class foo {
bar /* bar */();
baz /* baz */();
```html
}
// Prettier 3.9
class foo {
bar(); /* bar */
baz /* baz */();
}
修复抽象方法参数中尾部注释的问题(#19200,by @itsybitsybootsy)
// 输入
abstract class Foo {
abstract method(
param1: number,
// param2: number,
): void;
}
// Prettier 3.8
abstract class Foo {
abstract method(param1: number) // param2: number,
: void;
}
// Prettier 3.9
abstract class Foo {
abstract method(
param1: number,
// param2: number,
): void;
}
在 `no-semi` 模式下避免调用签名前出现多余分号(#19212,by @fisker)
// 输入
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>
(a: string): Promise<string>
}
// Prettier 3.8 (--no-semi)
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>;
(a: string): Promise<string>
}
// Prettier 3.9
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>
(a: string): Promise<string>
}
在类型别名声明中保留 `=` 后的注释位置(#19410,by @WhoamiI00)
// 输入
type A =
/* 1 */ | /* 2 */ (
/* 3 */ | /* 4 */ {
key: string;
}
);
// Prettier 3.8
type A /* 2 */ = /* 1 */ /* 3 */ /* 4 */ {
key: string;
};
// Prettier 3.9
type A = /* 1 */ /* 2 */ /* 3 */ /* 4 */ {
key: string;
};
```
修复实例化表达式周围缺少括号的问题(#19442,由 @fisker)
// 输入
async function* f() {
makeRecord = (yield* makeRecordFactory);
makeRecord = (yield makeRecordFactory);
makeRecord = (await makeRecordFactory);
}
// Prettier 3.8
async function* f() {
makeRecord = yield* makeRecordFactory;
makeRecord = yield makeRecordFactory;
makeRecord = await makeRecordFactory;
}
// Prettier 3.9
async function* f() {
makeRecord = (yield* makeRecordFactory);
makeRecord = (yield makeRecordFactory);
makeRecord = (await makeRecordFactory);
}
Flow
修复作为返回类型的可选函数的括号问题(#11004,#19331,由 @vjeux、@fisker)
// 输入
const fn = (a: number): ?((string) => string) => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};
// Prettier 3.8
const fn = (a: number): ?(string) => string => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};
// Prettier 3.9
const fn = (a: number): ?((string) => string) => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};
新增对 Flow 匹配实例模式及 Flow 记录的支持(#18511,由 @gkz)
// 输入
record R {
num: number,
}
const x = R {num: 1};
const label = match (x) {
R {num: 0} => "zero",
R {num: 1} => "one",
R {const num} => `${num} items`,
}
// Prettier 3.8
// 不支持
// Prettier 3.9
// 与输入相同
改善不确定元组中的注释格式 (#18616, @fisker 贡献)
// 输入
type A = [// comment
...];
type B = [/* comment */...];
// Prettier 3.8
type A = [...
// comment
];
type B = [...
/* comment */
];
// Prettier 3.9
type A = [
...// comment
];
type B = [.../* comment */];
修复组件中的悬空注释输出问题 (#18629, @fisker 贡献)
// 输入
component A(
// comment
) {}
component B(/* comment */) {}
// Prettier 3.8
component A() // comment
{}
component B /* comment */() {}
// Prettier 3.9
component A(
// comment
) {}
component B(/* comment */) {}
修复钩子中的悬空注释输出问题 (#18630, @fisker 贡献)
// 输入
hook A(
// comment
) {}
declare hook B(// comment
): void
type C = hook (// comment
)=> void
// Prettier 3.8
hook A() // comment
{}
declare hook B(): // comment
void;
type C = hook () => // comment
void;
// Prettier 3.9
hook A(
// comment
) {}
declare hook B(
// comment
): void;
type C = hook (
// comment
) => void;
支持隐式声明的函数和组件 (#18690, @fisker 贡献)
// 输入
declare module "foo" {
function greet(name: string): string;
component Button(label: string);
}
// Prettier 3.8
SyntaxError: Unexpected token `;`, expected the token `{` (2:39)
// Prettier 3.9
<与原输入相同>
改进多行长映射类型的格式化 (#18779, @kovsu 贡献)
// 输入
type MappedType = {
映射类型大括号换行优化(#18766 by @marcoww6)
// 输入
type MappedType<ValueType extends string | number> = {
[KeyType in ValueType extends string ? VeryLongStringTypeNameHere : VeryLongNumberTypeNameHere]: KeyType
}<br/><br/>// Prettier 3.8
type MappedType = {
[KeyType in ValueType extends string
? VeryLongStringTypeNameHere
: VeryLongNumberTypeNameHere]: KeyType,
};
// Prettier 3.9
type MappedType = {
[
KeyType in ValueType extends string
? VeryLongStringTypeNameHere
: VeryLongNumberTypeNameHere
]: KeyType,
};
为 keyof 类型操作符添加括号支持(#18801 by @marcoww6)
// 输入
type T1 = (keyof Foo)[];
type T2 = (keyof Foo)["bar"];
// Prettier 3.8
type T1 = keyof Foo[];
type T2 = keyof Foo["bar"];
// Prettier 3.9
type T1 = (keyof Foo)[];
type T2 = (keyof Foo)["bar"];
DeclareVariable 支持字面量初始化和多重声明(#18929 by @fisker)
// 输入
declare const x: string, y: number;
declare const s = 'foo';
// Prettier 3.8
SyntaxError: Unexpected token `,`, expected the token `;` (1:24)
// Prettier 3.9
declare const x: string, y: number;
declare const s = "foo";
组件声明打印 async 关键字(#19053 by @mvitousek)
// 输入
async component MyAsyncComponent() {}
// Prettier 3.8
component MyAsyncComponent() {}
// Prettier 3.9
async component MyAsyncComponent() {}
添加 writeonly、in 和 out 方差修饰符支持(#19102 by @marcoww6)
支持新的 Flow 方差语法:writeonly 用于对象类型属性/索引器,in T/out T 用于类型参数。
// 输入
type T = {writeonly foo: string};
```typescript
type Contravariant = T;
type Covariant = T;
// Prettier 3.8
// SyntaxError
// Prettier 3.9
type T = { writeonly foo: string };
type Contravariant = T;
type Covariant = T;
```
不要展开带行内注释的对象类型 (#19287 by @fisker)
// 输入
type T = {|/* comment */|};
// Prettier 3.8
type T = {|
/* comment */
|};
// Prettier 3.9
type T = {| /* comment */ |};
```
保留 Flow 注释语法 (#19398 by @SamChou19815)
保留 Flow 注释语法注解,而非将其打印为普通 Flow 语法。
// 输入
function foo<T>(bar /*: T[] */, baz /*: T */) /*: S */ {}
// Prettier 3.8
function foo<T>(bar: T[], baz: T): S {}
// Prettier 3.9
function foo<T>(bar /*: T[] */, baz /*: T */) /*: S */ {}
```
JSON
在 json-stringify 解析器中保留数字和字符串的原始表示 (#18405 by @fisker)
之前的 Prettier json-stringify 解析器使用 JSON.stringify() 来打印数字和字符串。这会导致在少数情况下丢失原始值的表示形式。例如,极大或极小的数字会被四舍五入,某些特殊字符会被转义消除。从技术上讲,这些变换不会改变 JSON 值的读取方式,但它们并非格式化程序 所关心的内容。Prettier 3.9 现在会保留原始表示,即使它可以被简化。
// 输入
[
"\u00FF",
1e9999,
0.4e669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006,
-9223372036854775809,
1e3,
{
1e3: 1,
1e999999999999999999999999999999: 1,
1: 1
}
]
// Prettier 3.8
[
"ÿ",
```
[
null,
null,
-9223372036854776000,
1000,
{
"1000": 1,
"Infinity": 1,
"1": 1
}
]
// Prettier 3.9
[
"\u00FF",
1e9999,
0.4e66999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006,
-9223372036854775809,
1e3,
{
1e3: 1,
1e999999999999999999999999999999: 1,
"1": 1
}
]
```
CSS
修复CSS属性值含字面换行时的断行问题 (#18605 by @kovsu)
```
/* 输入 */
div span[foo="a long long long long long long long long\<
long long long long attribute value"] {
}
/* Prettier 3.8 */
div
span[foo="a long long long long long long long long\<
long long long long attribute value"] {
}
/* Prettier 3.9 */
div span[foo="a long long long long long long long long\<
long long long long attribute value"] {
}
```
SCSS
防止在函数参数的数学表达式中添末尾逗号 (#18530 by @kovsu)
```
// 输入
@include container($foo: 2 * ($bar + $baz));
// Prettier 3.8
@include container(
$foo: 2 *
(
$bar + $baz,
)
);
// Prettier 3.9
@include container($foo: 2 * ($bar + $baz));
```
修复 map 中注释的打印问题 (#18535 by @kovsu)
```
// 输入
$map: (
// comment
);
// Prettier 3.8
$map: (// comment);
// Prettier 3.9
$map: (
// comment
);
```
避免给括号包裹的标量添加末尾逗号 (#19091 by @cyphercodes)
```
// 输入
.a { background-color: rgba($color: (#808080), $alpha: 0.9); }
// Prettier 3.8
.a {
background-color: rgba(
$color: (
```
#808080,
),
$alpha: 0.9
);
}
// Prettier 3.9
.a {
background-color: rgba($color: (#808080), $alpha: 0.9);
}
移除 SCSS if() 函数中 ; 分隔符前的空格 (#19384 by @kovsu)
// 输入
$value: if(sass($x): 1 ; else: 2);
// Prettier 3.8
$value: if(sass($x): 1 ; else: 2);
// Prettier 3.9
$value: if(sass($x): 1; else: 2);
HTML
修复 front matter 中 unicode 相关的一个 bug (#18453 by @seiyab)