← 文章 / 开源项目
prettier 3小时前 · 2026-09-03 22:27:21 · 0 阅读

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-meshiyaml-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 模式下 breakcontinue 周围的注释处理(#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 ) => {}; ```

修复 NewExpressionCallExpression 之间注释附加不一致的问题 (#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() {}

添加 writeonlyinout 方差修饰符支持(#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)

原始来源: prettier

评论 (0)