跳至主内容区

Prettier 3.7:改进的格式化一致性与全新插件功能!

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

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

我们很高兴宣布 Prettier 3.7 正式发布!本次版本更新重点优化了 TypeScript 和 Flow 的格式化体验,特别针对类和接口的格式进行了统一调整,使其更加一致且可预测。同时,我们希望就即将修复的类与接口体左花括号打印逻辑不一致问题征求您的意见。

此外,我们还修复了大量错误,新增了对 Angular 21 和 GraphQL 16.12 新特性的支持,并为 Handlebars 添加了 Front Matter 支持功能。

对于插件开发者,我们新增了 API 接口,让您能更灵活地控制注释附加逻辑和被忽略节点的处理方式。

若您认可 Prettier 的价值并希望支持我们的工作,欢迎通过我们的 OpenCollective直接赞助,或资助我们依赖的上下游项目。衷心感谢您持续的支持!

重要更新

TypeScript

修复类与接口之间的不一致打印 (#18094, #18091, #18215 by @fisker)

本次版本着重提升了类(Class)与接口(Interface)的格式化一致性。此前这两种相似结构的打印方式存在显著差异,导致视觉效果不统一。现在我们已经对齐了它们的格式化规则,提供更可预测且更清晰的输出效果。

移除了类中类型参数的额外缩进
// Input
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
extends A, B {}

declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}

// Prettier 3.6
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
> extends A,
B {}

declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}

// Prettier 3.7
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
extends A, B {}

declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}
将接口继承的打印方式与类对齐
// Input
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}

export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}

// Prettier 3.6
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>,
PointOverlayMixins<ES>,
LineOverlayMixins<ES> {}

export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}

// Prettier 3.7
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}

export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
将单一继承的打印方式与父类对齐
// Input
class ExtendsLongOneWithGenerics
extends
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}
class ExtendsLongOneWithGenerics
implements
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}
interface ExtendsLongOneWithGenerics
extends
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}

// Prettier 3.6
class ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
class ExtendsLongOneWithGenerics
implements
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
interface ExtendsLongOneWithGenerics
extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}

// Prettier 3.7
class ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
class ExtendsLongOneWithGenerics implements Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
interface ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
类与接口体的左花括号打印逻辑不一致

在 Prettier v2.3 中,为增强类头部与主体之间的视觉区分,当类具有多重继承时,我们开始将类主体的起始花括号 { 换行打印

// Prettier 2.2
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong {
property: string;
}

// Prettier 2.3
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong
{
property: string;
}

然而,并非所有人都满意这项改动。

欢迎在此 issue 中留言告知您对此变更应用于接口的看法。

若您有更优的解决方案,我们也非常乐意探讨。

除非出现更优方案,我们将在 Prettier v4 中将接口主体的打印方式与类主体对齐。

// Input
declare class loooooooooooooooooooong
implements looooooooooooooooooong, loooooooooooooooooooong {
property: string;
}

interface loooooooooooooooooooong
extends looooooooooooooooooong, loooooooooooooooooooong {
property: string;
}

// Prettier 3.7
declare class loooooooooooooooooooong
implements looooooooooooooooooong, loooooooooooooooooooong
{
property: string;
}

interface loooooooooooooooooooong
extends looooooooooooooooooong, loooooooooooooooooooong { // <-- This
property: string;
}

这些变更同样会影响 Flow 语法

其他变更

JavaScript

允许将导入属性(import attributes)换行显示 (#17329 by @fisker)

// Input
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with {
BABEL_8_BREAKING: "false",
USE_ESM: "true", IS_STANDALONE: "false" };

// Prettier 3.6
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with { BABEL_8_BREAKING: "false", USE_ESM: "true", IS_STANDALONE: "false" };

// Prettier 3.7
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with {
BABEL_8_BREAKING: "false",
USE_ESM: "true",
IS_STANDALONE: "false",
};

新增对"Discard Bindings"提案的支持 (#17708 by @fisker)

Stage 2 阶段的 "Discard Bindings" 提案现已通过 Babel 获得支持。请注意在使用此提案语法特性前,请先了解我们关于非标准语法的政策声明

const [void] = x;

const {x:void} = x;

修复注释格式不一致问题 (#17723 by @fisker)

// Input
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}

// Prettier 3.6 (--parser=typescript --experimental-operator-position=start)
if (
true
&& // This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
flibble.blibble.blobble?.bloo
) {
doThings();
}

// Prettier 3.6 (--parser=babel --experimental-operator-position=start)
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}

// Prettier 3.7
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}

新增 Playwright 测试函数支持 (#17876 by @BPScott)

当为测试函数添加 .skip 时,Prettier 已能避免改变其缩进格式。现在对于 Playwright 的 test.fixmetest.describe.skiptest.describe.fixme 函数,Prettier 也将其视为与 test.skip 类似进行处理。

// Input
test.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

test.describe.skip("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

test.describe.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

// Prettier 3.6
test.fixme(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);

test.describe.skip(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);

test.describe.fixme(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);

// Prettier 3.7
test.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

test.describe
.skip("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

test.describe
.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});

避免因长模块名导致 {import,require.resolve,require.resolve.paths,import.meta.resolve}() 换行 (#17882, #17908 by @kovsu & @fisker)

// Input
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b = require.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const c = require.resolve.paths("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");

const d = await import("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module")
const e = import.meta.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");

// Prettier 3.6
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b = require.resolve(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);
const c = require.resolve.paths(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);

const d = await import(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module"
);
const e = import.meta.resolve(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);

// Prettier 3.7
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b =
require.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const c = require.resolve
.paths("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");

const d =
await import("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const e = import.meta
.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");

优化 if 语句内的注释处理 (#17998 by @fisker)

// Input
if (foo) // comment
{
doThing();
} else // comment for else
{
doSomethingElse();
}

// Prettier 3.6
if (foo) {
// comment
doThing();
} // comment for else
else {
doSomethingElse();
}

// Prettier 3.7
if (foo) // comment
{
doThing();
} else // comment for else
{
doSomethingElse();
}

改进带注释的 require() 调用处理 (#18037 by @fisker)

// Input
require(
// Comment
"foo"
);

// Prettier 3.6
require(// Comment
"foo");

// Prettier 3.7
require(
// Comment
"foo",
);

移除 Boolean() 调用中逻辑表达式的缩进 (#18087 by @kovsu)

当将条件取反值或切换 !!Boolean() 用法时,减少代码差异。

// Input
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);

// Prettier 3.6
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition,
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);

// Prettier 3.7
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);

修复 for 语句的注释处理 (#18099 by @fisker, @sosukesuzuki)

// Input
for (x of y)
// Comment
bar();

// Prettier 3.6
// Comment
for (x of y) bar();

// Prettier 3.7
for (x of y)
// Comment
bar();

优化空语句周围的注释打印 (#18108 by @fisker)

// Input
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index ++
) /* No op */;

// Prettier 3.6
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index++ /* No op */
);

// Prettier 3.7
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index++
) /* No op */ ;

修复类方法与对象方法间注释打印不一致问题 (#18147 by @fisker)

// Input
class x {
method() // Class method
{
return 1
}
}

const object = {
method() // Object method
{
return 1
}
}

// Prettier 3.6
class x {
method() { // Class method
return 1;
}
}

const object = {
method() {
// Object method
return 1;
},
};

// Prettier 3.7
class x {
method() {
// class method
return 1;
}
}

const object = {
method() {
// object method
return 1;
},
};

为位运算符添加缺失的括号 (#18163 by @fs0414)

// Input
1 << (bit % 8);
1 >> (bit - 8);

// Prettier 3.6
1 << bit % 8;
1 >> (bit - 8);

// Prettier 3.7
1 << (bit % 8);
1 >> (bit - 8);

修复数组字面量换行不一致问题 (#18172 by @Dunqing, @fisker)

// Input
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides___(-1, -1), [1]);
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(-1, -1), [1, 2]);

// Prettier 3.6
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides___(-1, -1), [
1,
]);
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides(-1, -1),
[1, 2],
);

// Prettier 3.7
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides___(-1, -1),
[1],
);
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides(-1, -1),
[1, 2],
);

修复逻辑表达式打印不一致问题 (#18205 by @fisker)

// Input
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b
);

new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b
);

// Prettier 3.6
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);

new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);

// Prettier 3.7
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);

new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);

修复 CallExpressionNewExpression 打印不一致问题 (#18206 by @fisker)

// Input
TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection
)?.promptType
)

new TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection
)?.promptType
)

// Prettier 3.6
TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
);

new TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection,
)?.promptType,
);

// Prettier 3.7
TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
);

new TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
)

移除 JSX 元素周围冗余的括号 (#18243 by @fisker)

// Input
new A(
<div>
<div></div>
</div>
)

// Prettier 3.6
new A(
(
<div>
<div></div>
</div>
),
);

// Prettier 3.7
new A(
<div>
<div></div>
</div>,
);

改进作为 new 表达式调用者的逻辑表达式格式化 (#18245 by @fisker)

// Input
a = new (
a_long_long_long_long_condition || a_long_long_long_long_condition || a_long_long_long_long_condition
)();

// Prettier 3.6
a = new (a_long_long_long_long_condition ||
a_long_long_long_long_condition ||
a_long_long_long_long_condition)();

// Prettier 3.7
a = new (
a_long_long_long_long_condition ||
a_long_long_long_long_condition ||
a_long_long_long_long_condition
)();

移除 for 语句中缺少"更新"部分时的空行 (#18300 by @fisker)

// Input
for ( let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;i < len;) {}

// Prettier 3.6
for (
let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;
i < len;

) {}

// Prettier 3.7
for (
let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;
i < len;
) {}

改进父类格式 (#18325 by @fisker)

// Input
class EnsureNoDisposablesAreLeakedInTestSuiteSuite extends eslint.Rule.RuleModule {};

// Prettier 3.6
class EnsureNoDisposablesAreLeakedInTestSuiteSuite extends eslint.Rule
.RuleModule {}

// Prettier 3.7
class EnsureNoDisposablesAreLeakedInTestSuiteSuite
extends eslint.Rule.RuleModule {}

TypeScript

修复箭头函数后注释位置错误的问题 (#17421 by @o-m12a, @t-mangoe)

// Input
export const test = (): any => /* first line
second line
*/
null;

// Prettier 3.6
export const test = (): any /* first line
second line
*/ => null;

// Prettier 3.6 (Second format)
SyntaxError: Unexpected token (1:22)
> 1 | export const test = (): any /* first line
| ^
2 | second line
3 | */ => null;
4 |

// Prettier 3.7
export const test = (): any =>
/* first line
second line
*/
null;

为实例化表达式中的箭头函数添加缺失的括号 (#17724 by @fisker)

// Input
void (<_T extends never>() => {})<never>;

// Prettier 3.6
void <_T extends never>() => {}<never>;

// Prettier 3.7
void (<_T extends never>() => {})<never>;

修复 TSMappedType 格式 (#17785 by @fisker)

// Input (--parser=babel-ts)
export type A = B extends { C?: { [D in infer E]?: F } } ? G : H

// Prettier 3.6
TypeError: Cannot read properties of undefined (reading 'startsWith')

// Prettier 3.7
export type A = B extends { C?: { [D in infer E]?: F } } ? G : H;

TSImportType 选项中打印尾部逗号 (#17798 by @fisker)

在 TypeScript v5.9 之前,导入类型属性中不允许使用尾部逗号,现在 这个 bug 已被修复。

// Input
type A = import("foo", {
with:{
type:'json',
} // <- Should be a comma here
})

// Prettier 3.6
type A = import("foo", {
with: {
type: "json",
} // <- Should be a comma here
});

// Prettier 3.7
type A = import("foo", {
with: {
type: "json",
}, // <- Should be a comma here
});

改进带注释的 CommonJS 模块 require() 的格式化 (#18035 by @fisker)

// Input
import foo = require(
// Comment
"foo"
);

// Prettier 3.6
import foo = require(// Comment
"foo");

// Prettier 3.7
import foo = require(
// Comment
"foo"
);

在类型参数中的 = 后换行 (#18043 by @fisker)

// Input
export type OuterType2<
LongerLongerLongerLongerInnerType = LongerLongerLongerLongerLongerLongerLongerLongerOtherType
> = { a: 1 };

// Prettier 3.6
export type OuterType2<
LongerLongerLongerLongerInnerType = LongerLongerLongerLongerLongerLongerLongerLongerOtherType,
> = { a: 1 };

// Prettier 3.7
export type OuterType2<
LongerLongerLongerLongerInnerType =
LongerLongerLongerLongerLongerLongerLongerLongerOtherType,
> = { a: 1 };

移除联合类型前意外的空行 (#18109 by @jspereiramoura, @fisker)

// Input
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

// Prettier 3.6
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =

| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment

| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

// Prettier 3.7
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

此更改同样影响 Flow 语法

修复 typescriptflow 解析器之间注释打印不一致的问题 (#18110 by @fisker)

// Input
interface A {
a: // Comment
B;
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
& Fooo1000
& Baz2000
& BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}

// Prettier 3.6 (--parser=typescript)
interface A {
a: // Comment
B;
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
Fooo1000 &
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}

// Prettier 3.6 (--parser=flow)
interface A {
a: B; // Comment
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: Fooo1000 & // Comment
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}

// Prettier 3.7 (Same output for `--parser=typescript` and `--parser=flow`)
interface A {
a: B; // Comment
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
Fooo1000 &
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}

修复调用签名前缺少分号的问题 (#18118 by @fisker)

// Input
interface A {
foo;
<T>(): T;
}
type B = {
foo;
<T>(): T;
}

// Prettier 3.6 (--no-semi, first format)
interface A {
foo
<T>(): T
}
type B = {
foo
<T>(): T
}

// Prettier 3.6 (--no-semi, second format)
interface A {
foo<T>(): T
}
type B = {
foo<T>(): T
}

// Prettier 3.7
interface A {
foo;
<T>(): T
}
type B = {
foo;
<T>(): T
}

修复 flow 和 typescript 解析器之间 as const 打印不一致的问题 (#18161 by @fisker)

// Input
1 as /* comment */ const;

// Prettier 3.6 (--parser=typescript)
1 as /* comment */ const;

// Prettier 3.6 (--parser=flow)
1 /* comment */ as const;

// Prettier 3.7 (Same output for `--parser=typescript` and `--parser=flow`)
1 /* comment */ as const;

修复 as/satisfies 表达式周围的注释 (#18162 by @fisker)

// Input
1 as /*
comment
*/
const;

// Prettier 3.6 (First format)
1 /*
comment
*/ as const;

// Prettier 3.6 (Second format)
SyntaxError: Unexpected keyword or identifier. (3:4)
1 | 1 /*
2 | comment
> 3 | */ as const;
| ^
4 |

// Prettier 3.7
1 as const /*
comment
*/;

修复联合类型对齐错误 (#18165 by @fisker)

// Input
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}

// Prettier 3.6
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression];
}

// Prettier 3.7
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression];
}

移除联合类型中多余的空行 (#18197 by @Dunqing)

// Input
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

// Prettier 3.6
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =

| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

// Prettier 3.7
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;

Flow

为"match"语法添加基础支持 (#17681 by @fisker)

// Input
const e = match (a) {
1 => true,
'foo' => false,
2 => {obj: 'literal'},
};

// Prettier 3.6
SyntaxError: Unexpected token `{`, expected the token `;` (1:21)
> 1 | const e = match (a) {
| ^
2 | 1 => true,
3 | 'foo' => false,
4 | 2 => {obj: 'literal'},

// Prettier 3.7
const e = match (a) {
1 => true,
"foo" => false,
2 => { obj: "literal" },
};

支持带上下界的opaque类型 (#17792 by @SamChou19815)

// Input
opaque type Counter super empty extends Box<T> = Container<T>;
opaque type Counter super Box<T> = Container<T>;
declare opaque type Counter super empty extends Box<T>;
declare opaque type Counter super Box<T>;

// Prettier 3.6
SyntaxError: Unexpected identifier, expected the token `=` (1:21)

// Prettier 3.7
opaque type Counter super empty extends Box<T> = Container<T>;
opaque type Counter super Box<T> = Container<T>;
declare opaque type Counter super empty extends Box<T>;
declare opaque type Counter super Box<T>;

CSS

处理带大小写敏感标志和大写不敏感标志的属性选择器 (#17841, #17865 by @kovsu)

/* Input */
[type=a s],
[type=a S],
[type=a I] {
list-style-type: lower-alpha;
}

/* Prettier 3.6 */
[type="a s"],
[type="a S"],
[type="a I"] {
list-style-type: lower-alpha;
}

/* Prettier 3.7 */
[type="a" s],
[type="a" S],
[type="a" I] {
list-style-type: lower-alpha;
}

修复格式化特殊自定义属性时的崩溃问题 (#17899 by @fisker)

/* Input */
:root {
--l: , #000;
}

/* Prettier 3.6 */
TypeError: Cannot read properties of undefined (reading 'value')

/* Prettier 3.7 */
:root {
--l: , #000;
}

修复CSS模块内选择器被错误转换为小写的问题 (#17929 by @kovsu)

/* Input */
:export {
nest: {
myColor: blue;
}
myColor: red;
}

/* Prettier 3.6 */
:export {
nest: {
mycolor: blue;
}
myColor: red;
}

/* Prettier 3.7 */
:export {
nest: {
myColor: blue;
}
myColor: red;
}

修复包含//的CSS选择器格式化问题 (#17938 by @kovsu)

/* Input */
a[href="http://example.com"] {
color: red;
}

/* Prettier 3.6 */
a[href="http://example.com"]
{
color: red;
}

/* Prettier 3.7 */
a[href="http://example.com"] {
color: red;
}

移除字体尺寸与行高之间的多余空格 (#18114 by @kovsu)

/* Input */
a {
font: var(--size)/1;
}

/* Prettier 3.6 */
a {
font: var(--size) / 1;
}

/* Prettier 3.7 */
a {
font: var(--size)/1;
}

修复块注释后CSS逗号分隔值的额外缩进问题 (#18228 by @seiyab)

/* Input */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}

/* Prettier 3.6 */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}

/* Prettier 3.7 */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}

SCSS

修复mixin参数中的括号间距问题 (#17836 by @kovsu)

// Input
.foo {
@include bar('A (B)');
}

// Prettier 3.6
.foo {
@include bar('A( B)');
}

// Prettier 3.7
.foo {
@include bar('A (B)');
}

修复空格分隔值的格式化问题 (#17903 by @kovsu)

// Input
.foo {
@include transition(min-height ($spacer/2) ease-in-out);
}

// Prettier 3.6
.foo {
@include transition(min-height($spacer/2) ease-in-out);
}

// Prettier 3.7
.foo {
@include transition(min-height ($spacer/2) ease-in-out);
}

Less

修复变量名被错误转换为小写的问题 (#17820 by @kovsu)

// Input
@fooBackground:line-gradient(#f00);

a {
background: @fooBackground;
}

// Prettier 3.6
@foobackground:line-gradient (#f00);

a {
background: @fooBackground;
}


// Prettier 3.7
@fooBackground: line-gradient(#f00);

a {
background: @fooBackground;
}

保持属性/变量访问器的紧凑格式 (#17983 by @kovsu)

// Input
.average(@x, @y) {
@result: ((@x + @y) / 2);
}

div {
padding: .average(16px, 50px) [ @result ];
}

// Prettier 3.6
.average(@x, @y) {
@result: ((@x + @y) / 2);
}

div {
padding: .average(16px, 50px) [ @result];
}

// Prettier 3.7
.average(@x, @y) {
@result: ((@x + @y) / 2);
}

div {
padding: .average(16px, 50px)[@result];
}

HTML

支持格式化iframe元素的 allow 属性 (#17879 by @kovsu)

<!-- Input -->
<iframe allow="layout-animations 'none'; unoptimized-images 'none'; oversized-images 'none'; sync-script 'none'; sync-xhr 'none'; unsized-media 'none';"></iframe>

<!-- Prettier 3.6 -->
<iframe allow="layout-animations 'none'; unoptimized-images 'none'; oversized-images 'none'; sync-script 'none'; sync-xhr 'none'; unsized-media 'none';"></iframe>

<!-- Prettier 3.7 -->
<iframe
allow="
layout-animations 'none';
unoptimized-images 'none';
oversized-images 'none';
sync-script 'none';
sync-xhr 'none';
unsized-media 'none';
"
></iframe>

格式化内联事件处理程序 (#17909 by @kovsu)

<!-- Input -->
<button
type="button"
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
>Press me</button>

<!-- Prettier 3.6 -->
<button
type="button"
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
>
Press me
</button>

<!-- Prettier 3.7 -->
<button
type="button"
onclick="console.log('Hello, this is my old-fashioned event handler!')"
>
Press me
</button>

Angular

支持 Angular 21 (#17722, #18294 by @fisker)

Angular 20.1 增加了对新赋值运算符的支持。 Angular 21 增加了对正则表达式的支持。

<!-- Input -->
<b (click)="
a ??= b">{{ /\d+/g}}</b>

<!-- Prettier 3.6 -->
<b
(click)="
a ??= b"
>{{ /\d+/g}}</b
>

<!-- Prettier 3.7 -->
<b (click)="a ??= b">{{ /\d+/g }}</b>

修复插值中注释重复的问题 (#17862 by @fisker)

<!-- Input -->
{{a() // comment}}

<!-- Prettier 3.6 -->
{{ a(// comment) // comment }}

<!-- Prettier 3.7 -->
{{
a() // comment
}}

修复"非空断言"的格式化问题 (#18047 by @fisker)

<!-- Input -->
{{ foo?.bar!.baz }}

<!-- Prettier 3.6 -->
{{ (foo?.bar)!.baz }}

<!-- Prettier 3.7 -->
{{ foo?.bar!.baz }}

Ember / Handlebars

为 Handlebars 添加 Front Matter 支持 (#17781 by @Codezilluh)

Handlebars 现在可以使用 Front Matter。

---
title: My page title
keywords:
- word
- other word
---

<h1>{{title}}</h1>
<ul>
{{#each keywords}}
<li>{{this}}</li>
{{/each}}
</ul>

为自定义助手保留 else if 语法 (#17856 by @kovsu)

{{! Input }}
{{#animated-if this.foo}}
foo content
{{else if (this.bar)}}
bar content
{{/animated-if}}

{{! Prettier 3.6 }}
{{#animated-if this.foo}}
foo content
{{else}}{{#if (this.bar)}}
bar content
{{/if}}{{/animated-if}}

{{! Prettier 3.7 }}
{{#animated-if this.foo}}
foo content
{{else if (this.bar)}}
bar content
{{/animated-if}}

移除 <style> 标签内的多余空行 (#18065 by @kovsu, @fisker)

{{! Input }}
<style>
#foo {
color: red;
}
</style>

{{! Prettier 3.6 (--html-whitespace-sensitivity=ignore) }}
<style>

#foo {
color: red;
}

</style>

{{! Prettier 3.7 (--html-whitespace-sensitivity=ignore) }}
<style>
#foo {
color: red;
}
</style>

--html-whitespace-sensitivity=ignore 时不再强制元素换行 (#18133 by @fisker)

{{! Input }}
<div> </div>

{{! Prettier 3.6 (--html-whitespace-ensitivity=ignore) }}
<div>
</div>

{{! Prettier 3.7 (--html-whitespace-ensitivity=ignore) }}
<div></div>

GraphQL

支持"可执行描述" (#18214 by @fisker)

# Input
"Description"
query {
node {
id
}
}

# Prettier 3.6
SyntaxError: Syntax Error: Unexpected description, descriptions are supported only on type definitions. (1:1)
> 1 | "Description"
| ^
2 | query {
3 | node {
4 | id

# Prettier 3.7
"Description"
query {
node {
id
}
}

Markdown

改进表情符号尺寸测量 (#17813 by @seiyab)

此项改进提升了所有语言中表格的对齐效果。

<!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |

<!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |


<!-- Prettier 3.7 -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |

为 Front Matter 推断 TOML 解析器 (#17965 by @kovsu)

TOML Front Matter 可通过相应插件处理。 同样适用于 HTML 和 CSS 文件。

修复强强调格式 (#18010 by @yin1999)

这是对 #17143 的补充修复,原 PR 无法确定左右两侧内容是否均为单词。

<!-- Input -->
1***2***3
1**_2_**3

<!-- Prettier 3.6 -->
1**_2_**3
1**_2_**3

<!-- Prettier 3.7 -->
1***2***3
1***2***3

MDX

修复 importexport 解析 (#17996 by @kovsu & @fisker)

{/* Input */}
- import is a word in lists
- export is a word in lists, too!

{/* Prettier 3.6 */}
- import is a word in list
s
- export is a word in lists, too
!

{/* Prettier 3.7 */}
- import is a word in lists
- export is a word in lists, too!

YAML

保留映射和注释间的空行 (#17843 by @kovsu)

# Input
only: issues

# Comment

# Prettier 3.6
only: issues
# Comment

# Prettier 3.7
only: issues

# Comment

保留显式的文档结束标记 (#18296 by @fisker)

# Input
a: a
---
b: b
...
c: c
...
---
d: d

# Prettier 3.6
a: a
---
b: b
---
c: c
---
d: d

# Prettier 3.7
a: a
---
b: b
...
c: c
...
---
d: d

对含尾部注释的流映射键使用显式键样式 (#18324 by @kovsu, @fisker)

# Input
{ "foo" # comment
:bar }

# Prettier 3.6
{ "foo": bar } # comment

# Prettier 3.7
{ ? "foo" # comment
: bar }

API

允许 plugin.parser.preprocess() 返回 Promise (#17679 by @fisker)

plugin.printer.preprocess() 保持一致,后者允许返回 Promise

我们仍建议将异步工作移至 plugin.parser.parse() 中处理(该函数本身已支持返回 Promise),未来可能会移除对 plugin.parser.preprocess() 的支持。

允许 AstPath#call() 访问空值属性的成员 (#17860 by @fisker)

此前要检查可能不存在的子节点,必须先确认节点存在:

const isFoo = path.call(() => path.node?.type === "Foo", "foo", "bar");
// Uncaught TypeError: Cannot read properties of undefined (reading 'bar')

必须进行如下操作:

const isFoo =
path.node.foo?.bar &&
path.call(() => path.node?.type === "Foo", "foo", "bar");

自 Prettier 3.7 起,访问空值属性的成员不再抛出错误。

plugin.printer.canAttachComment() 传递祖先节点信息 (#18055 by @fisker)

这能防止将注释附加到节点的特定子节点上。

例如在 JavaScript 代码 const object = {property}; 中,Identifier 节点 (property) 在 AST 中同时作为 Property.keyProperty.value 出现,显然它只能被打印一次。若注释附加到不可打印的子节点上,注释将会丢失。

插件现在可通过添加如下 canAttachComment 函数避免此问题:

export const canAttachComment = (node, [parent]) =>
!(
parent?.type === "Property" &&
parent.shorthand &&
parent.key === node &&
parent.key !== parent.value
);

export const print = (path, options, print) => {
const { node } = path;
switch (node.type) {
case "Property":
if (node.shorthand) {
return print("value");
}
// ...
}
};

新增对 plugin.printer.printPrettierIgnored() 的支持 (#18070 by @fisker)

对于带有 prettier-ignore 注释的节点,Prettier 会直接打印该节点的文本。但这可能引发问题,例如当节点需要添加括号,或在 --no-semi 模式下需要打印前置分号来避免自动分号插入(ASI)问题时。

自 Prettier 3.7 起,插件可向 printer 添加 printPrettierIgnored() 函数,用于自定义被 prettier-ignore 节点的打印流程。该函数采用与 plugin.printer.print() 完全相同的签名。

允许插件提供 estree 打印器 (#18072 by @fisker)

此前,若插件开发者希望基于内置 printer 创建输出不同代码的 estree printer,则必须同时提供 parsersprinters。Prettier 3.7 允许插件开发者创建仅提供 estree printer 的插件。

CLI

避免在未启用 --cache 时创建 node_modules/.cache/ 目录 (#18124 by @chiawendt)

在不使用 --cache 参数的情况下运行 prettier . 时,将不再创建空的 node_modules/.cache/ 目录。